Clojure 루프에서 let'd 변수 재정의
문제
좋아요.나는 Clojure를 만지작거리고 있었고 계속해서 같은 문제에 직면했습니다.이 작은 코드 조각을 살펴보겠습니다.
(let [x 128]
(while (> x 1)
(do
(println x)
(def x (/ x 2)))))
이제 다음과 같이 128로 시작하는 시퀀스가 인쇄될 것으로 예상됩니다.
128
64
32
16
8
4
2
대신 128을 계속해서 인쇄하는 무한 루프입니다.분명히 내가 의도한 부작용이 작동하지 않습니다.
그렇다면 이와 같은 루프에서 x 값을 어떻게 재정의해야 할까요?나는 이것이 Lisp와 같지 않을 수도 있다는 것을 알고 있습니다(아마도 자체적으로 재귀하는 익명 함수를 사용할 수 있습니다). 그러나 이와 같은 변수를 설정하는 방법을 찾지 못하면 미칠 것입니다.
내 다른 추측은 set!을 사용하는 것이지만 바인딩 형식이 아니기 때문에 "잘못된 할당 대상"이 표시됩니다.
이것이 어떻게 작동하는지 알려주십시오.
해결책
def
일부 코드의 함수 또는 내부 루프에서 사용하더라도 Toplevel var를 정의합니다. 당신이 들어가는 것 let
VARS가 아닙니다. 당 문서 let
:
LET로 만든 현지인은 변수가 아닙니다. 일단 생성 된 가치는 결코 변하지 않습니다!
(내 강조가 아닙니다.) 당신은 여기서 당신의 예를 위해 변한 상태가 필요하지 않습니다. 당신은 사용할 수 있습니다 loop
그리고 recur
.
(loop [x 128]
(when (> x 1)
(println x)
(recur (/ x 2))))
당신이 화려하고 싶다면 당신은 명시 적을 피할 수 있습니다 loop
전적으로.
(let [xs (take-while #(> % 1) (iterate #(/ % 2) 128))]
(doseq [x xs] (println x)))
만약 너라면 진짜 Mutable State를 사용하고 싶었습니다 원자 작동 할 수도 있습니다.
(let [x (atom 128)]
(while (> @x 1)
(println @x)
(swap! x #(/ %1 2))))
(당신은 필요하지 않습니다 do
; while
당신을 위해 명백한 몸에 몸을 감싸십시오.) 정말, 정말 이 작업을 원했습니다 vars 당신은 이런 끔찍한 일을해야합니다.
(with-local-vars [x 128]
(while (> (var-get x) 1)
(println (var-get x))
(var-set x (/ (var-get x) 2))))
그러나 이것은 매우 못 생겼으며 관용적 인 망토가 아닙니다. Clojure를 효과적으로 사용하려면 돌연변이 상태의 관점에서 생각을 중단해야합니다. 비 기능적 스타일로 Clojure 코드를 작성하려고 노력하는 것은 분명히 당신을 미치게 할 것입니다. 잠시 후에는 실제로 변수 변수가 실제로 얼마나 드물는지 즐거운 놀라움이 될 수 있습니다.
다른 팁
vars (당신이 "def"something)를 할 때 얻을 수있는 것입니다.
user=> (def k 1)
#'user/k
user=> k
1
당신이하는 것을 막는 것은 없습니다.
user=> (def k 2)
#'user/k
user=> k
2
스레드-로컬 정착 가능한 "장소"를 원한다면 "바인딩"및 "세트!"를 사용할 수 있습니다.
user=> (def j) ; this var is still unbound (no value)
#'user/j
user=> j
java.lang.IllegalStateException: Var user/j is unbound. (NO_SOURCE_FILE:0)
user=> (binding [j 0] j)
0
그러면 다음과 같은 루프를 쓸 수 있습니다.
user=> (binding [j 0]
(while (< j 10)
(println j)
(set! j (inc j))))
0
1
2
3
4
5
6
7
8
9
nil
그러나 나는 이것이 꽤 일체형이라고 생각합니다.
함수가 여전히 순수하게 유지되기 때문에 순수 함수에 변경 가능한 지역 변수를 갖는 것이 해를 끼치지 않는 멋지고 편리한 기능이라고 생각한다면 Rich Hickey가 언어에서 해당 변수를 제거한 이유를 설명하는 이 메일링 리스트 토론에 관심이 있을 것입니다. . 왜 변경 가능한 로컬이 아닌가?
관련 부분:
지역 주민이 변수인 경우, 즉돌연변이가 발생하면 폐쇄는 돌연변이 상태를 초과하여 폐쇄 될 수 있으며, 폐쇄가 탈출 할 수 있다는 점을 감안할 때 (동일하게도 추가 금지없이) 결과는 스레드-미트가 될 것입니다.그리고 사람들은 확실히 그렇게 할 것입니다클로저 기반 의사 객체.결과는 Clojure의 접근 방식에 큰 구멍이 될 것입니다.
돌연변이 가능한 현지인이 없으면 사람들은 기능적 루핑 구조 인 Recur를 사용해야합니다.처음에는 이상하게 보일 수 있지만, 이는 변이가 있는 루프처럼 간결하며, 결과 패턴은 다음과 같습니다 clojure의 다른 곳에서 재사용되는 경우, 즉반복, 축소, 변경, 출퇴근 등 는 모두 (논리적으로) 매우 유사합니다.비록 내가 감지하고 돌연변이 폐쇄가 빠져나가는 것을 방지하기 위해 다음과 같이 유지하기로 결정했습니다 를 사용하여 일관성을 유지하세요.가장 작은 컨텍스트에서도 비변이 루프는 다음과 같습니다 돌연변이보다 이해하고 디버깅하기 쉽습니다.어쨌든, Vars 는 적절한 경우 사용할 수 있습니다.
후속 게시물의 대부분은 구현에 관한 것입니다. with-local-vars
매크로;)
당신은 더 독창적으로 사용할 수 있습니다 iterate
그리고 take-while
대신에,
user> (->> 128
(iterate #(/ % 2))
(take-while (partial < 1)))
(128 64 32 16 8 4 2)
user>