为什么这个Haskell代码永远不会终止?

时间:2014-11-12 21:35:27

标签: haskell lazy-evaluation

我最近编写了一些Haskell代码,它永远不会终止。在仔细检查了我的代码后,问题归结为以下代码

main :: IO ()
main = print $ let a = 10 in
               let a = a in
               a :: Int

我想这必然与Haskell的懒惰有关,因为相同的代码在OCaml中终止。但是,如果我写了以下代码

main :: IO ()
main = print $ let a = 10 in
               let b = a in
               b :: Int

代码完全没有问题终止。我无法得到原因,因为在原始代码中,两个a应该被视为两个不同的变量。我不知道为什么它们的命名与程序的语义有关。

2 个答案:

答案 0 :(得分:15)

问题在于,与OCaml不同,Haskell中的let绑定默认为递归。因此let x = x in ...等同于OCaml let rec x = x in ...,并且是循环定义。

这就是为什么在Haskell中隐藏变量名称(即多次定义a)被认为是错误的样式,甚至还有一个编译器警告,您可以使用-Wall标志打开或更具体地{ {1}}。

这个默认值在Haskell中比OCaml更有意义,因为由于懒惰,循环值(而不仅仅是递归函数)实际上是有用的。 -fwarn-name-shadowing为我们提供了一个let x = 1:x的无限列表,我们可以像普通列表一样使用它。

同时,有些人不喜欢这样,基本上就是你遇到的原因:它可能会在你的代码中引入不直观的无限循环,这会让一些错误和拼写错误变得更难追查。这也令人困惑,因为默认情况下,do-notation 中的1绑定不是递归,这有点不一致。

答案 1 :(得分:5)

第二个绑定(a = a)会影响另一个绑定。第一个例子(几乎)完全等同于

main = print $ let xyz = 10 in
               let a = a in
               a :: Int

我希望很清楚为什么不终止!您可以使用-fwarn-name-shadowing标记(或在GHCi中输入:set -fwarn-name-shadowing)让GHC向您发出警告