是的,if 你的do 块中有不止一行,if 你甚至使用了do 符号。
完整的do-notation 语法还包括显式分隔符——花括号和分号:
main = do { putStrLn "What's your name?"
; name <- getLine
; putStrLn ("Hello " ++ name)
}
使用它们,缩进除了在编码风格中起任何作用(良好的缩进提高可读性;显式分隔符确保代码的健壮性,消除与空白相关的脆弱性)。所以当你只有一行 IO 代码时,比如
main = do { print "Hello!" }
没有分号,没有需要注意的缩进,花括号和do关键字本身就变得多余了:
main = print "Hello!"
所以,不,并非总是如此。但通常情况下确实如此,而且代码的一致性对可读性大有帮助。
do 块转换为一元代码,但首先您可以将此事实视为实现细节。事实上,你应该。您可以在心理上将do 符号公理视为嵌入式语言。再说了,反正就是这样。
简化的do-语法为:
do { pattern1 <- action1
; pattern2 <- action2
.....................
; return (.....)
}
对于某些 monad M 和某些结果类型 a<sub>i</sub>,每个 action<sub>i</sub> 都是 M a<sub>i</sub> 类型的 Haskell 值。每个action 产生自己的结果类型a<sub>i</sub>,而all actions 必须属于相同 monad 类型M。
每个pattern<sub>i</sub> 都会从相应的操作接收先前“计算”的结果。
通配符_ 可以用来忽略它。如果是这种情况,_ <- 部分可以完全省略。
“Monad”是一个可怕且无信息的词,但从概念上讲,它实际上只不过是 EDSL。 嵌入式领域特定语言意味着我们有本地 Haskell 值代表(在这种情况下)I/O 计算。我们用这种语言编写我们的 I/O 程序,它成为一个原生 Haskell 值,我们可以像对任何其他原生 Haskell 值一样对其进行操作——将它们收集到列表中,将它们组合成更复杂的计算描述(程序)等
main 值就是我们的 Haskell 程序计算的一个这样的值。编译器看到它,并在运行时执行它所代表的 I/O 程序。
关键是我们现在可以拥有一个“函数”getCurrentTime(从表面上看,在函数范式中是不可能的,因为它必须在不同的调用中返回不同的结果),因为它不是 返回当前时间——它描述的动作将这样做,当它描述的I/O程序运行时运行时系统。
在类型级别上,这些值反映的不仅仅是一些普通的 Haskell 类型a,而是一个参数化类型,IO a,被IO“标记”为属于这个特殊的 世界I/O 编程。
另请参阅:Why does haskell's bind function take a function from non-monadic to monadic。