为什么我会出现堆栈溢出?

时间:2017-04-09 16:49:11

标签: haskell recursion

所以,我尝试实现自己的ModExp实现(我知道我应该使用现有的实现,但它是学校的任务)并且我在使用大数字时会出现堆栈溢出(> = 2 ^ 1024)

modExp :: Integer -> Integer -> Integer -> Integer
modExp x y m
    | y == 0 = 1
    | y `mod` 2 == 0 = modExp ((x ^ 2) `mod` m) (y `div` 2) m
    | otherwise = x * modExp x (y - 1) m `mod` m

有什么问题?我听说你必须使用尾递归来避免堆栈溢出,但在这里我有两种必须调用递归的情况。我该如何解决这个问题?

1 个答案:

答案 0 :(得分:4)

你的函数是单递归的,所以如果我们确保它也是尾递归的,我们可以保持它的内存大小。诀窍不是产生更多出色的操作。守卫在y中使函数严格,所以我们知道这不是任何等待发生的任意计算树。但x随调用深度而变化,在我们生成返回值之前不需要,返回值有三种情况:常量,尾递归和递归两个操作(*和{{1在电话会议之外。那些操作将进行某种调用堆栈。

解决这个问题可能是两步操作:首先,使函数完全是尾递归的。其次,要严格要求,以便热切评估而不是构建越来越复杂的表达式。

尾递归变体:

mod

在某些情况下,在modExp :: Integer -> Integer -> Integer -> Integer modExp x y m = modExp' x y m 1 where modExp' x y m acc | y == 0 = acc | y `mod` 2 == 0 = modExp' ((x ^ 2) `mod` m) (y `div` 2) m acc | otherwise = modExp' x (y - 1) m (x * acc `mod` m) x中强制执行严格性可能也很有用。可以使用acc

来完成
seq

稍微简单的变体可以将modExp :: Integer -> Integer -> Integer -> Integer modExp x y m = modExp' x y m 1 where modExp' x y m acc | y == 0 = acc | y `mod` 2 == 0 = let x' = (x ^ 2) `mod` m y' = y `div` 2 in x' `seq` modExp' x' y' m acc | otherwise = let acc' = x * acc `mod` m in acc' `seq` modExp' x (y - 1) m acc' 应用于seqx本身,这原则上可能会使一层计算未完成,但不会更多。无论哪种方式都强制执行恒定的空间

不幸的是我没有使用ghc 7.10.3重现你的堆栈溢出,所以我不确定哪些长度证明是必要的。