4

다음의 재귀 적 정의를 사용하여 Clojure (JVM)와 ClojureScript (브라우저 연결 repl와 lumo 모두에서 테스트 됨) REPL은 두 개의 다른 출력, 즉 노드가 인쇄되는 순서 Clojure REPL은 :f이라는 중복을 생성합니다. ClojureScript 순서는 예상 한 동작입니다. 왜 이런거야?Clojure와 ClojureScript REPL은 서로 다른 출력을 생성합니다.

코드 :

(defn dfs 
    ([g v] (dfs g v #{})) 
    ([g v seen] 
    (println v) 
    (let [seen (conj seen v)] 
    (for [n (v g)] 
     (if-not (contains? seen n) 
     (dfs g n seen)))))) 

(def graph {:a [:b :c :e] 
      :b [:d :f] 
      :c [:g]}) 

(dfs graph :a) 

Cloure REPL 출력 :

:a 
:b 
:c 
:e 
:d 
:f 
:g 
:f 
;; => ((()()) (()) (())) 

CLojureScript REPL 출력 :

:a 
:b 
:d 
:f 
:c 
:g 
:e 
;; => ((()()) (())()) 
+2

clojure 1.8과 1.9-alpha14에서 모두 끝에 : f가없고 내 parens는 cljs parens와 동일합니다. – Josh

+0

그건 이상한데, 나는 Clojure 1.8.0과 ClojureScript 1.9.229를 사용하고있다. – mac

답변

6

Clojure에서의 for 게으른 시퀀스를 생성합니다. 모든 재발행 dfs 호출의 실제 평가는 기능 출력 (예 : ((()()) (())()))을 인쇄해야하므로 REPL에서만 트리거됩니다. (do (dfs graph :a) nil)을 평가하면 :a 만 인쇄됩니다.

이제 Clojure의 지연 시퀀스는 효율성을 위해 evaluated in chunks of size 32입니다. 따라서 REPL (str 함수를 통해)이 첫 번째 요소 지연 시퀀스 최상위 레벨 for (:b을 인쇄해야 함)을 평가할 때 해당 seq의 다른 요소도 평가되고 자식 노드의 시퀀스가 ​​인쇄되기 전에 :c:e이 인쇄됩니다. 평가했다 (게으른 너무).

대조적으로 Clojurescript의 지연 시퀀스는 청크 (LazySeq does not implement IChunkedSeq)가 아니며 하나씩 평가되므로 반환 값을 재귀 적으로 문자열로 변환하면 모든 것이 깊이 우선 평가됩니다.

REPL에서 Clojure와 CLJS 모두 (first (for [i (range 300)] (do (println "printing:" i) i)))을 시도해보십시오. 클로저에는 32 개의 숫자가 인쇄되고 CLJS에는 하나의 번호 만 인쇄됩니다.

평가 순서를 더 잘 보장하려면 for 대신 doseq을 사용하거나 doallfor을 입력하십시오.

희망이 도움이됩니다.

사이드 참고 : 단지 @Josh로, 나는 Clojure의 1.8에서 결국에는 :f를 얻을 수없고, 괄호는 cljs 출력과 동일하다 - ... 그건 정말 이상 하네

난 다음입니다 확실하지 않다 현재 DFS의 결과를 사용하는 방법. 부작용을 사용하고 싶다면, i. 이자형. 그들이 통과되어 있는지 확인 doseq를 사용하여 콘솔에 모든 노드를 인쇄 :

(defn dfs-eager 
    ([g v] (dfs-eager g v #{})) 
    ([g v seen] 
    (println v) 
    (let [seen (conj seen v)] 
    (doseq [n (v g)] 
     (if-not (contains? seen n) 
     (dfs-eager g n seen)))))) 

이 깊이 우선, 콘솔에 모든 노드를 인쇄합니다. 당신이 반환 값으로 순회를 얻고 싶다면, for를 사용하지만 실제로 의미있는 값을 반환해야합니다 :

(defn dfs-lazy 
    ([g v] (dfs-lazy g v #{})) 
    ([g v seen] 
    (cons v 
     (let [seen (conj seen v)] 
      (for [n (v g)] 
      (if-not (contains? seen n) 
       (dfs-lazy g n seen))))))) 

당신은 중첩 된 목록 (:a (:b (:d) (:f)) (:c (:g)) (:e)) 얻을 것이다 - 당신이 다음 순회를 얻기 위해 평평하게 할 수 있습니다. 또한 게으름의 혜택을 얻을 수 있습니다.

+0

답변 해 주셔서 감사합니다. 평가 순서가 청킹의 영향을 받는다는 것은 나에게 미친 것처럼 보인다. 또한 왜'''v'' 만 인쇄 할 때 다른 요소가 REPL에 출력 될까요? ''println''' 호출을 제거하면 아무 것도 출력되지 않습니다. – mac

+0

문제는 부작용이있는 코드와 게으른 코드가 섞여 있다는 것입니다. 귀하의 경우, 평가의 순서 (그리고 사실)는 덩어리뿐만 아니라 REPL이 함수의 반환 값을 출력하는지 여부에 달려 있습니다 ((()() (())())'to 콘솔 - 당신이 그것에 대해 생각하면 그 자체로 미친 것입니다. 결과를 반환 값으로 사용하려면 결과를 콘솔에 출력하려는 ​​경우 명령형 코드를 사용하거나 결과를 반환 값으로 사용하려면 기능/게으른 코드를 사용할 수 있습니다 (위의 내 게시물을 편집 할 때 몇 가지 예를 참조하십시오). 그러나 두 가지 접근 방식을 혼합하는 것은 적절한 Clojure가 아닙니다. –

관련 문제