Próbując nauczyć się Haskella, zaimplementowałem obliczenie pi, aby poprawnie zrozumieć funkcje i rekurencję.Przepełnienie stosu w GHCI podczas próby wyświetlenia numeru
Używanie Leibniz Formula obliczania PI, wpadłem z następujących, które drukuje PI tolerancji danego parametru, a liczba funkcji rekurencyjnej wzywa, aby uzyskać tę wartość:
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)
więc kiedy biegnę to w GHCI, wydaje się działać zgodnie z oczekiwaniami:
*Main> piCalc 0.001
(3.1420924036835256,2000)
Ale jeśli ustawić moja tolerancja zbyt dobrze, to się dzieje:
*Main> piCalc 0.0000001
(3.1415927035898146,*** Exception: stack overflow
Wydaje mi się to całkowicie sprzeczne z intuicją; Rzeczywiste obliczenia działają poprawnie, ale po prostu próbuje wydrukować, jak wiele połączeń rekursywnych nie powiedzie się?
Dlaczego tak się dzieje?
Jeśli nie wiesz, co to jest thunk (nie zrobiłem tego, kiedy zacząłem Haskell!), Jest to w zasadzie nierozwiązana kalkulacja. W twoim pierwszym przykładzie, zanim wydrukujesz 'count', nie będzie mieć wartości' 2000', będzie miała wartość '... + 1) +1) +1) +1) +1)' (Pominąłem lewe nawiasy na początku: P). Kiedy to drukujesz, jest on faktycznie dodawany. –
Po prostu dodam do tego, co @DanielBuckmaster powiedział, że ważne jest to, że tony wciąż się budują, zabierają coraz więcej pamięci, podczas gdy jeden naiwnie oczekuje, że 'count' będzie czymś w rodzaju' Int' (stała w przestrzeni) . Przyzwyczaisz się do tego, ale na pewno coś cię ugryzie. – gspr