Haskell中的main函数是否总是以main = do开头?

7
在Java中,我们总是编写以下代码:
public static void main(String[] args){...}

当我们想要开始编写程序时。

我的问题是,对于Haskell而言是否也是如此,即:当我想在Haskell中编写程序代码时,我总是可以声明main = do吗?

例如:

main = do  
    putStrLn "What's your name?"  
    name <- getLine 
    putStrLn ("Hello " ++ name) 

这个程序将会询问用户:“你叫什么名字?” 用户输入将会被存储在名字变量中,然后在程序终止之前显示“你好” ++ 名字。


5
"main" - 是的。但是"do"是程序体的一部分,可能存在也可能不存在。请参见这里 - Eugene Sh.
1
main = putStrLn "It depends on how large 'main' is." - atravers
2个回答

14

简短回答不需要声明do,但必需声明main =

main必需是IO单子类型(即IO a),其中a是任意的(因为它被忽略了),如这里所写:

  

使用名称main很重要:main被定义为 Haskell 程序的入口点(类似于 C 中的 main 函数),并且必须具有IO类型,通常是IO()

但你不一定需要do符号。实际上,do语法糖。事实上,你的main可以写成:

main =
    putStrLn "What's your name?" >> getLine >>= \n -> putStrLn ("Hello " ++ n)

更加优雅的说法:

main = putStrLn "What's your name?" >> getLine >>= putStrLn . ("Hello " ++)

所以在这里我们已经写了一个没有使用do符号的main函数。关于如何对do符号进行desugaring(语法糖展开),可以参考这里


1
在 Haskell 中,“单子”一词是指类型(如 IO),而不是值(如 main)。 - Li-yao Xia
1
严格来说,Haskell程序的入口点不能命名为main(您可以通过传递-main-is <symbol>来更改此设置),但当然这不是问题的重点。 - user2407038
1
@user2407038 严格来说,它仍然是一个 Haskell 程序还是一个不完全符合 Haskell 的程序? - user253751
1
使用除了 () 以外的任何东西的主要原因是为了表明程序只能通过显式退出或接收异常来退出。在这种情况下,它可以被赋予多态类型 IO a - dfeuer

4

如果您在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 的绑定函数需要从非单子到单子的函数

实际上,do表示法支持三种类型的语句:绑定语句(使用<-),let语句(没有"in"的let语句)和纯单子表达式(既没有<-也没有"in")。只有最后一种语句被允许作为最后一条语句。 - Paul Stelian
1
答案中写着“简化的 do-语法”(重点添加)。您不需要使用完整规范来介绍某些内容。有时候,即使是小谎言也是介绍某些东西的最佳方式(这在这里也是如此,因为do甚至可以在一些边缘情况下用于非单调值,比如do 1)。do let总是可以重写为普通的let,而非绑定单调值的do语句则可以重写为_ <-绑定语句。答案甚至说:“_ <-部分可以完全省略”。 - Will Ness
1
关于在 do 块中允许的最后一个语句,我的回答更进一步并建议总是以 return ... 结束它,作为一种简化。这也总是有效的,感谢单子律。@PaulStelian - Will Ness

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接