这不是尾递归,所以 Haskell 最终也会崩溃。
fac :: Int -> Int
fac n = if n == 0 then 1 else n*fac(n-1)
(答案不适合 Int,只是让它更快地传递崩溃)
留下正确性的问题,只需运行fac 10000000 并看到它因堆栈溢出而崩溃。
这是尾递归的:
fac :: Int -> Int
fac n = g 1 n where g a n = if n == 0 then a else g (a*n) (n-1)
不会崩溃。 (但也不是正确答案,因为使用了 Int)
(另外,正如 cmets 中正确指出的那样,如果我们将函数保留为默认的 Integer -> Integer 类型,它将使用不受 CPU 架构约束的整数。但从那时起计算将花费更长的时间,它非尾递归最终崩溃将需要更长的时间来满足我们自己。)
在这里的 cmets 中,有人抱怨 g 在 a 中是懒惰的。虽然一般来说这是一个问题,但这不是重点,在这种特殊情况下没有区别:
> ghc -O2 -ddump-simpl a.hs > a.dump.lazy
...
Rec {
Main.$wg [Occ=LoopBreaker]
:: GHC.Prim.Int# -> GHC.Prim.Int# -> GHC.Prim.Int#
[GblId, Arity=2, Caf=NoCafRefs, Str=DmdType LL]
Main.$wg =
\ (ww_s11J :: GHC.Prim.Int#) (ww1_s11N :: GHC.Prim.Int#) ->
case ww1_s11N of wild_Xn {
__DEFAULT ->
Main.$wg (GHC.Prim.*# ww_s11J wild_Xn) (GHC.Prim.-# wild_Xn 1);
0 -> ww_s11J
}
end Rec }
现在,相同,但在 a 中使 g 严格:
fac :: Int -> Int
fac n = g 1 n where
g !a n = if n == 0 then a else g (a*n) (n-1)
> ghc -O2 -XBangPatterns -ddump-simpl a.hs > a.dump.eager
...
Rec {
Main.$wg [Occ=LoopBreaker]
:: GHC.Prim.Int# -> GHC.Prim.Int# -> GHC.Prim.Int#
[GblId, Arity=2, Caf=NoCafRefs, Str=DmdType LL]
Main.$wg =
\ (ww_s11P :: GHC.Prim.Int#) (ww1_s11T :: GHC.Prim.Int#) ->
case ww1_s11T of wild_Xs {
__DEFAULT ->
Main.$wg (GHC.Prim.*# ww_s11P wild_Xs) (GHC.Prim.-# wild_Xs 1);
0 -> ww_s11P
}
end Rec }
显然,优化器可以看到g的唯一返回值是a,所以让它变得懒惰没有任何好处是a。