2017-01-06 4 views
2

Clojure에서 함수형 프로그래밍을 배우려고합니다. 많은 함수형 프로그래밍 자습서는 불변성의 이점으로 시작하며 일반적인 예로는 명령형 언어의 루프 변수가 있습니다. 그 점에서 Clojure의 loop-recur은 어떻게 다릅니 까? 예를 들어 :Clojure - Loop 변수 - Immutability

(defn factorial [n] 
    (loop [curr-n n curr-f 1] 
    (if (= curr-n 1) 
     curr-f 
     (recur (dec curr-n) (* curr-f curr-n))))) 

curr-n 긴급한 스타일의 언어로 루프 변수에 curr-f 변경 가능한 값 유사한 아닌가?

+1

정신 모형과 관련하여 이름을 새로운 값으로 리 바인딩하는 것으로 재귀를 생각하십시오. 값은 여전히 ​​변경 가능하지 않으며, 그 값 중 하나를 어딘가에 저장하면 원자, & c),'recur'가 원래의 것을 rebind하기 때문에 발생하지 않습니다. –

답변

3

썸네일이 가리키는 것처럼, clojure에서 loop-recur을 사용하면 고전적인 재귀 함수 호출과 동일한 형태와 효과가 나타납니다. 그것이 존재하는 유일한 이유는 순수 재귀보다 훨씬 효율적이라는 것입니다.

recur은 꼬리 위치에서만 발생할 수 있으므로 loop "변수"는 절대로 다시 필요하지 않습니다. 따라서 스택에 보존 할 필요가 없으므로 중첩 된 함수 호출과 달리 재귀 적 또는 비 순차적 스택이 사용되지 않습니다. 결과적으로 &은 다른 언어의 명령형 루프와 매우 유사하게 동작합니다. for 루프 모두 "변수", "변경"에 한정된다는 점이다 Java 스타일 비교

개선 loop 식 초기화시 상기 recur 식 업데이트 때. vars에 대한 변경 사항은 루프 본문이나 다른 곳에서는 발생할 수 없습니다 (예 : Java에서 루프 변수를 변경할 수있는 포함 된 함수 호출).

"루프 바 (loop vars)"가 변이/업데이트 될 수있는 위치에 대한 이러한 제한 사항으로 인해 실수로 버그를 변경할 기회가 줄어 듭니다. 제한의 비용은 루프가 전통적인 Java 스타일 루프만큼 유연하지 않다는 것입니다.

이 비용 편익 교환이 다른 비용 편익 상충 관계보다 유용한 시점을 결정하는 것은 사용자의 몫입니다.당신은 순수 자바 스타일의 루프를 원하는 경우, 자바 변수를 시뮬레이션하기 Clojure에서 atom 사용하기 쉬운 :

; Let clojure figure out the list of numbers & accumulate the result 
(defn fact-range [n] 
    (apply * (range 1 (inc n)))) 
(spyx (fact-range 4)) 

; Classical recursion uses the stack to figure out the list of 
; numbers & accumulate the intermediate results 
(defn fact-recur [n] 
    (if (< 1 n) 
    (* n (fact-recur (dec n))) 
    1)) 
(spyx (fact-recur 4)) 

; Let clojure figure out the list of numbers; we accumulate the result 
(defn fact-doseq [n] 
    (let [result (atom 1) ] 
    (doseq [i (range 1 (inc n)) ] 
     (swap! result * i)) 
    @result)) 
(spyx (fact-doseq 4)) 

; We figure out the list of numbers & accumulate the result 
(defn fact-mutable [n] 
    (let [result (atom 1) 
     cnt (atom 1) ] 
    (while (<= @cnt n) 
     (swap! result * @cnt) 
     (swap! cnt inc)) 
    @result)) 
(spyx (fact-mutable 4)) 

(fact-range 4) => 24 
(fact-recur 4) => 24 
(fact-doseq 4) => 24 
(fact-mutable 4) => 24 

심지어 우리가 적어도 각, 자바 가변 변수를 에뮬레이트하는 원자를 사용하는 마지막 경우 우리가 눈에 띄게 swap! 기능으로 표시 한 것을 돌연변이시켜 "우발적 인"돌연변이를 놓치기 어렵게 만듭니다.

P. spyx을 사용하고 싶다면 in the Tupelo library

2

curr-n 및 필수적 스타일의 언어로 루프 변수에 curr-f 변경 가능한 값 유사한 아닌가?

아니요. loop-recur을 재귀 함수 호출로 언제든지 다시 쓸 수 있습니다. 예를 들어, factorial 기능이 느리고 큰 숫자에-스택 오버 플로우 될 수 있습니다

(defn factorial [n] 
    ((fn whatever [curr-n curr-f] 
    (if (= curr-n 1) 
     curr-f 
     (whatever (dec curr-n) (* curr-f curr-n)))) 
    n 1)) 

... 다시 작성할 수 있습니다. 이 전화를 incarnating의 순간에 올 때


recur 대신 새로 할당의 한 및 전용 스택 프레임을 덮어 씁니다. 이것은 호출자의 스택 프레임이 이후에 절대 참조되지 않는 경우에만 작동합니다. 즉, 이라는 꼬리 위치는입니다.

loop은 구문 설탕입니다. 나는 이것이 매크로라는 것을 의심 스럽지만 그것이 될 수있다. 단, 이전 바인딩은 let에서와 같이 이후 바인딩에서 사용할 수 있어야합니다. 그러나이 문제는 현재 부적절하다고 생각합니다.

+1

올바르게 호출하면 결과 바이트 코드에서 실제로 변형되므로 실제 루프로 컴파일됩니다. –

+0

당신의 대답에 그것을 붙여 넣으면 전적으로 그것에 투표 할 것입니다. –