【问题标题】:Python (maximum recursion depth exceeded) vs Haskell (finds out the answer)Python(超出最大递归深度)vs Haskell(找出答案)
【发布时间】:2013-12-06 21:17:00
【问题描述】:

所以我开始学习一点 Haskell。在得到递归定义后,我将阶乘定义编码为:

let fac n = if n==0 then 1 else n*fac(n-1)

(编码方式完全不同,我知道 :))

我觉得这和python定义是一样的:

def fac(n): 
  if n==0:
    return 1
  else:
    return n*fac(n-1)

我的问题是关于 python 抛出的最大递归深度错误。虽然这2个函数的编码方式是一样的,但是是什么让python在n=1000时会抛出错误而haskell计算结果呢?

【问题讨论】:

  • Python 有一个最大递归深度,默认为 1000。Haskell 没有。这是设计理念的问题;对于 Python 中大多数实用的递归算法来说,1000 就足够了,但 Haskell 更强调递归。
  • 我认为这是关于他们耗尽处理器的方式,感谢您的回答:)

标签: python haskell recursion


【解决方案1】:

这不是尾递归,所以 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 中,有人抱怨 ga 中是懒惰的。虽然一般来说这是一个问题,但这不是重点,在这种特殊情况下没有区别:

> 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

【讨论】:

  • 添加,为了获得正确的行为,类型应该是Integer -> Integer,因为Int是由系统限定的,而Integer是一个任意大小的整数。
  • 由于 g 在 a 中是惰性的,这不会在 thunk 溢出中仍然崩溃吗?当你真正需要foldl'时,你基本上建议foldl代替foldr
  • @ollanta 当然。问题是关于递归的,所以我解释了尾递归的不同之处。另外,一个很好的问题是:当内存不足时(在 OutOfMemory 崩溃之前)它会崩溃还是开始减少 thunk?
  • @jozefg 当然。我首先有Integer -> Integer,但随后即使使非尾递归崩溃也需要很长时间。我会补充这一点。
猜你喜欢
  • 2011-12-31
  • 1970-01-01
  • 2020-07-13
  • 2021-02-07
  • 2017-03-18
  • 2015-04-15
  • 1970-01-01
  • 2013-12-01
相关资源
最近更新 更多