【问题标题】:Does the main-function in Haskell always start with main = do?Haskell 中的 main 函数是否总是以 main = do 开头?
【发布时间】:2017-06-23 18:25:59
【问题描述】:

在java中我们总是这样写:

public static void main(String[] args){...}

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

我的问题是,Haskell IE 是否也一样:我是否可以始终确保声明: main = 当我想在 Haskell 中为程序编写代码时?

例如:

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

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

【问题讨论】:

  • main - 是的。但是do 是身体的一部分,它可能存在也可能不存在。见here
  • main = putStrLn "It depends on how large 'main' is."

标签: haskell main do-notation


【解决方案1】:

简答,我们必须声明main =,但不是do

main 必须是 IO monad 类型(所以IO a)其中a 是任意的(因为它被忽略了),正如@所写987654321@:

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

但您不需要do 符号。其实do is syntactical sugar。你的main其实是:

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

或者更优雅:

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

所以我们在这里写了一个没有do 符号的main。有关 desugaring do 表示法的更多信息,请参阅 here

【讨论】:

  • 在 Haskell 中,“monad”一词指的是类型(如 IO),而不是值(如 main)。
  • 严格来说,Haskell 程序的入口点不能命名为main(您可以通过传递-main-is &lt;symbol&gt; 来更改它)但这当然不是问题的重点。跨度>
  • @user2407038 严格来说,它仍然是一个 Haskell 程序还是一个不太完全的 Haskell 程序?
  • 使用() 以外的任何东西的主要原因是为了传达程序仅显式退出或通过接收异常退出。在这种情况下,它可以被赋予一个多态类型,IO a
【解决方案2】:

是的,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> 都会从相应的操作接收先前“计算”的结果。

通配符_ 可以用来忽略它。如果是这种情况,_ &lt;- 部分可以完全省略。


“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

【讨论】:

  • 实际上 do 表示法支持 3 种类型的语句:绑定(带有
  • 它在答案中说“simplified do-syntax”(强调添加)。您没有介绍其完整规格的东西。有时最好的介绍东西的方法就是撒谎(这里也是这种情况,因为do 甚至适用于极端情况下的非单子值,例如do 1)。 do let 总是可以像正常的let 和非绑定单值do 语句一样重写为_ &lt;- 绑定之一。答案甚至说“_ &lt;- 部分可以完全省略”。
  • 至于do 块中允许的最后一条语句,我的回答更进一步,建议始终以return ... 结尾,作为简化。由于单子定律,这也总是有效的。 @PaulStelian
猜你喜欢
  • 2021-12-06
  • 1970-01-01
  • 1970-01-01
  • 2014-10-06
  • 1970-01-01
  • 2020-10-02
  • 2021-05-06
  • 1970-01-01
  • 2018-03-06
相关资源
最近更新 更多