2013-01-30 1 views
8

하스켈을 배우려고 할 때 함수와 재귀를 올바르게 이해하기 위해 pi 계산을 구현했습니다.숫자를 표시하려고 시도 할 때 GHCI의 스택 오버플로

파이를 계산하는 Leibniz Formula을 사용하여, 나는 주어진 파라미터의 허용 오차에 파이를 인쇄 다음, 함께했다, 그리고 재귀 함수의 수는 그 값을 얻기 위해 호출

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 

이것은 완전히 반 직관적 인 것처럼 보입니다. 실제 계산은 정상적으로 작동하지만 재귀 호출이 얼마나 실패했는지 인쇄하려고합니다.

왜 이렇게됩니까?

+3

썽크가 무엇인지 모를 경우 (하스켈을 시작했을 때 나는하지 않았다!) 기본적으로 미해결의 계산이다. 첫 번째 예에서는'count'를 출력하기 전에'2000'의 값을 가지지 않고'... + 1) +1의 값을가집니다 +1) +1) +1) +1)'' (나는 시작에서 2000 개의 왼쪽 괄호를 생략했다 : P). 인쇄 할 때 실제로 추가됩니다. –

+2

@DanielBuckmaster가 중요한 점은 덩어리가 계속해서 더 많은 메모리를 사용하고있는 반면, 하나는 순식간에 '카운트'가 'Int'(우주의 상수) . 당신은 이것에 익숙해 질 것입니다. 그러나 확실히 당신을 물 수있는 것입니다. – gspr

답변

8

이것은 foldl (+) 0 [1..1000000] 스택 오버 플로우의 변형입니다. 문제는 카운트 값이 piCalc'의 평가 중에 평가되지 않는다는 것입니다. 즉, 필요에 따라 추가 작업을 나타내는 썽크 세트가 계속 늘어남을 의미합니다. 필요할 때 스택 갯수에 비례하여 스택 깊이가 필요하다는 사실로 인해 오버플로가 발생합니다.

가장 간단한 해결 방법이 그것이 성장하지 않습니다 즉, 패턴이 일치 할 때 평가 될 count의 값을 강제로

piCalc' denom prevPi tolerance !count = ... 

piCalc'의 시작을 변경의 BangPatterns 확장을 사용합니다 썽크의 거대한 사슬.

동등하게, 그리고 확장을 사용하지 않고, 당신은

piCalc' denom prevPi tolerance count = count `seq` ... 

이 의미 위의 솔루션을 정확히 동등로 쓸 수 있지만, 언어의 확장을 통해 명시 적으로 대신 암시의 seq 사용합니다. 이렇게하면 이식성은 향상되지만 좀 더 자세한 정보가 표시됩니다. newPi, prevPitolerance의 값을 요구하는 연산의 결과에 piCalc' 분기 : 원주율의 근사 중첩 썽크 긴 시퀀스 아니지만 카운트 이유로서는

.완료되었는지 또는 다른 반복을 실행해야하는지 결정하기 전에 해당 값을 검사해야합니다. 함수가 수행 될 때 평가가 수행되도록하는 분기입니다 (함수 응용 프로그램이 수행 될 때 일반적으로 함수의 결과에 패턴 일치가 있음을 의미합니다). 반면에 piCalc'의 계산에는 아무 것도 값에 의존하지 않습니다. count이므로 계산 중에 계산되지 않습니다.

+1

이 예제에서 썽킹이 pi의 계산 된 값으로 발생하지 않는 이유를 설명 할 수 있습니까? –

+2

@DanielBuckmaster 왜냐하면'piCalc '는'newPi','prevPi','tolerance'의 값을 필요로하는 연산의 결과로 분기하기 때문입니다. 완료되었는지 또는 다른 반복을 실행해야하는지 결정하기 전에 해당 값을 검사해야합니다. 평가가 수행되는 지점입니다 (함수 응용 프로그램을 수행 할 때 일반적으로 함수의 결과에 패턴 일치가 있음을 의미). – Carl

+1

감사합니다! 나는 대답하는 것이 매우 가치 있다고 생각합니다. 이것이'count'가 실제 계산이 아닌 스택 오버 플로우를 일으키는 이유입니다. –

10

이 계산은 계산 중에 평가되지 않으므로 끝까지 엄청난 양의 썽크 (스택 오버플로)로 남아 있습니다.

당신은 BangPatterns 확장을 가능하게하고 piCalc' denom prevPi tolerance !count = ...

을 작성하여 계산하는 동안의 평가를 강제 할 수 그런데 왜 우리는 count의 평가를 강제로해야합니까? 음, 다른 모든 주장은 if에서 평가됩니다. piCalc'에 다시 전화하기 전에 실제로 모든 것을 검사해야하므로 썽크가 쌓이지는 않습니다. 우리는 실제 값을 필요로합니다. 단지 "계산할 수 있다는 것을 약속하지"않습니다! 반면에 count은 계산 과정에서 절대로 필요하지 않으며 마지막까지 일련의 썽크로 남을 수 있습니다.