Haskellを学ぼうとして、関数と再帰を正しく理解するためにpi計算を実装しました。パイを計算するためLeibniz Formulaを使用して数字を表示しようとするとGHCIでスタックオーバーフローが発生する
、私は与えられたパラメータの許容範囲にパイを印刷し、再帰関数の数は、その値を取得するために呼び出す、次を思い付いた:
reverseSign :: (Fractional a, Ord a) => a -> a
reverseSign num = ((if num > 0
then -1
else 1) * (abs(num) + 2))
piCalc :: (Fractional a, Integral b, Ord a) => a -> (a, b)
piCalc tolerance = piCalc' 1 0.0 tolerance 0
piCalc' :: (Ord a, Fractional a, Integral b) => a -> a -> a -> b -> (a, b)
piCalc' denom prevPi tolerance count = if abs(newPi - prevPi) < tolerance
then (newPi, count)
else piCalc' (reverseSign denom) newPi tolerance (count + 1)
where newPi = prevPi + (4/denom)
だから私はGHCiの中でこれを実行すると、期待どおりに動作するようです:
*Main> piCalc 0.001
(3.1420924036835256,2000)
をしかし、私は私の許容範囲があまりにも細かい設定されている場合、この処理が行われます。
*Main> piCalc 0.0000001
(3.1415927035898146,*** Exception: stack overflow
これは私には完全に直感的なようです。実際の計算は正常に動作しますが、再帰呼び出しが何回失敗するかを印刷しようとしていますか?
これはなぜですか?
場合、あなたは(私はHaskellのを始めたとき、私はしませんでした!)サンクがあるかわからない、それは基本的に未解決の計算です。あなたの最初の例では、 'count'を印刷する前に' 2000'の値を持たず、 '... + 1)+1の値を持ちます+1)+1)+1)' ' (私は開始時に2000の左括弧を省きました:P)。それを印刷すると、実際には追加されます。 –
@DanielBuckmasterは、Thunkが構築し続けているメモリの重要なポイントは、メモリがますます増えていく一方で、「カウント」は「Int」(空間内の定数) 。あなたはこれに慣れますが、間違いなくあなたを噛むことができます。 – gspr