year.hs:
year = year + 1
main = print year
这不是尾递归调用:
year = year + 1
year = (year + 1) + 1
year = ((year + 1) + 1) + 1
...
但是runhaskell year.hs
没有输出任何内容,这表明它会遇到无限循环。
Haskell编译器是否对非尾递归调用进行优化?
答案 0 :(得分:19)
由于懒惰(加上单态限制†)。基本上当你说
时year = year + 1
然后评估year
,Haskell为结果保存空间,然后当它看到year
时,它会尝试重复使用相同的结果。因此,当year + 1
尝试评估year
时,year
的代码实际上并未输入。
在GHC中,要实现多线程‡,它实际上做的是在尝试获取已经被评估的变量的值时阻止当前线程。然后,当评估完成时,它将继续执行被阻止的线程。在这种情况下,线程在它自己正在进行的评估上被阻塞,这就是你遇到死锁的原因。
如果您改为说
year () = year () + 1
然后运行year ()
会给我一个堆栈溢出。
†单态限制发挥作用,因为如果你添加一个类型签名
year :: Num a => a
year = year + 1
编译器可以完全自由地将Num a
字典视为()
参数,从而产生堆栈溢出。在这种情况下,这不是一个真正的问题,但不缓存中间结果是实际计算中的一个大问题。在这种情况下,看起来GHC实际上将递归放在抽象的字典中,产生更像
year () = let y = y + 1 in y
也不会产生堆栈溢出。
<小时/> ‡在单线程模式下编译代码(使用GHC)会产生
<<loop>>
这意味着GHC检测到无限循环,并决定抱怨它。