我最近编写了一些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应该被视为两个不同的变量。我不知道为什么它们的命名与程序的语义有关。
答案 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向您发出警告