如果您在do
块中有多行代码,而且使用了do
符号,则是可以的。
do
符号的完整语法还包括明确的分隔符——大括号和分号:
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 { pattern<sub>1</sub> <- action<sub>1</sub>
; pattern<sub>2</sub> <- action<sub>2</sub>
.....................
; return (.....)
}
每个
actioni
都是一个 Haskell 值,类型为
M ai
,其中
M
是某个单子类型,
ai
是某个结果类型。每个
action
都会产生自己的结果类型
ai
,而
所有 的
action
必须属于同一单子类型
M
。
每个
patterni
从相应的 action 接收之前“计算”出来的结果。
通配符
_
可以用来忽略它。如果是这种情况,则可以完全省略
_ <-
部分。
“Monad” 是一个可怕且不具信息性的词,但在概念上实际上只是 EDSL。嵌入式领域特定语言意味着我们有本地的 Haskell 值代表(在本例中)
I/O 计算。我们在这种语言中编写我们的
I/O 程序,它们变成本地的 Haskell 值,我们可以像对待其他本地的 Haskell 值一样对它们进行操作——将它们收集到列表中,将它们组合成更复杂的计算描述(程序),等等。
main
值是我们的 Haskell 程序计算出来的一个这样的值。编译器看到它,并在运行时执行它所代表的
I/O 程序。
重点是我们现在可以有一个“函数”
getCurrentTime
(在函数式范式中不可能,因为它必须在单独的调用上返回不同的结果),因为它并没有返回当前时间——它所描述的操作将会在运行时系统运行它所描述的
I/O 程序时返回当前时间。
在类型级别上,这反映在这些值不仅具有某些普通的 Haskell 类型
a
,而且还具有参数化类型
IO a
,由
IO
标记为属于这个特殊的
I/O 编程世界。
另请参见:
为什么 haskell 的绑定函数需要从非单子到单子的函数。
main = putStrLn "It depends on how large 'main' is."
- atravers