所以,我尝试实现自己的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
有什么问题?我听说你必须使用尾递归来避免堆栈溢出,但在这里我有两种必须调用递归的情况。我该如何解决这个问题?
答案 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'
应用于seq
或x
本身,这原则上可能会使一层计算未完成,但不会更多。无论哪种方式都强制执行恒定的空间
不幸的是我没有使用ghc 7.10.3重现你的堆栈溢出,所以我不确定哪些长度证明是必要的。