【问题标题】:Why this Haskell code never terminates?为什么这个 Haskell 代码永远不会终止?
【发布时间】:2015-01-09 21:34:26
【问题描述】:

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

【问题讨论】:

    标签: haskell lazy-evaluation


    【解决方案1】:

    问题在于,与 OCaml 不同,Haskell 中的let 绑定默认情况下是递归的。所以let x = x in ...等价于OCaml的let rec x = x in ...,是一个循环定义。

    这就是为什么在 Haskell 中隐藏变量名称(即多次定义 a)被认为是不好的风格,甚至会有编译器警告,您可以使用 -Wall 标志或更具体地使用 -fwarn-name-shadowing 来打开它。

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

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

    【讨论】:

    • <- 的情况要糟糕得多。
    • @yatima2975 我以前用过do rec。这实际上是解决某些问题的一种干净的方法。
    【解决方案2】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-26
      • 1970-01-01
      • 1970-01-01
      • 2013-06-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多