消耗内存的尾递归函数

时间:2014-11-27 00:28:00

标签: haskell memory recursion ghc tail-recursion

我有一个明确的尾递归函数来查找(选择n k)mod 10007(k非负)

为什么此功能会为大输入消耗大量内存? (即100000000选择50000000)我可以理解它是否可能很慢,但它不应该使用超过常量内存,应该吗? (假设GHC知道尾调用优化)

GHC版本7.8.3

modulus :: Int
modulus = 10007

choose :: Int -> Int -> Int
choose n1 k1
    | s1 > 0 = 0
    | otherwise = q1
  where
    (q1, s1) = doChoose n1 k1 (1, 0)
    doChoose :: Int -> Int -> (Int, Int) -> (Int, Int)
    doChoose _ 0 (qr, sr) = (qr, sr)
    doChoose n k (qr, sr) =
        doChoose (n `seq` (n-1)) (k-1) (qr `seq` (qn * qr `rem` modulus * inv qk `rem` modulus), sr `seq` (sn + sr - sk))
      where
        (qn, sn) = removePs n
        (qk, sk) = removePs k

removePs :: Int -> (Int, Int)
removePs n =
  case r of
    0 -> (q0, s0 + 1)
    _ -> (n, 0)
  where
    (q, r) = n `quotRem` modulus
    (q0, s0) = removePs q

inv :: Int -> Int
inv = doInv 0 1 modulus . (`mod` modulus)
  where
    doInv x _ 1 0
      | x < 0 = x + modulus
      | otherwise = x
    doInv _ _ _ 0 = error "Not relatively prime"
    doInv x y a b = doInv y (x - q * y) b r
      where
        (q, r) = a `quotRem` b

2 个答案:

答案 0 :(得分:5)

我把seq放在错误的地方。

需要:

n `seq` qr `seq` sr `seq` doChoose (n-1) (k-1) (qn * qr `rem` modulus * inv qk `rem` modulus, sn + sr - sk)

否则在达到基本情况之前不会对seq的调用进行评估,并且仍会建立一系列thunk。

这不是严格的尾递归,而是它是“相互”尾递归的,因为seq最终返回它的第二个参数而不修改它。

答案 1 :(得分:-1)

顺便说一下,为了简化你的表达式,你可以编写一个辅助函数:

force x = x `seq` x

或使用force包中的Deepseq(无双关语)。然后

doChoose (force n - 1) (k - 1) (qn * force qr * etc.)