Clojure 和 ClojureScript REPL 产生不同的输出
Clojure and ClojureScript REPL produce different output
使用深度优先搜索的以下递归定义 Clojure (JVM) 和 ClojureScript(使用浏览器连接的 repl 和 lumo 进行测试)REPL 产生两个不同的输出,即打印节点的顺序不同,Clojure REPL产生重复的 :f
。 ClojureScript 命令是我所期望的行为。这是为什么?
代码:
(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
;; => ((() ()) (()) ())
Clojure 的 for
生成惰性序列。所有循环 dfs
调用的实际评估仅由您的 REPL 触发,因为它需要打印函数的输出,即 ((() ()) (()) ())
。如果你计算 (do (dfs graph :a) nil)
,你只会打印 :a
。
现在,Clojure 的惰性序列 evaluated in chunks of size 32 提高了效率。因此,当 REPL(通过 str
函数)评估第一个元素惰性序列的顶层 for
(应该打印 :b
)时,该序列的其他元素也会被评估,你在评估子节点的序列(也是惰性的)之前打印 :c
和 :e
。
相比之下,Clojurescript 的惰性序列不分块(LazySeq does not implement IChunkedSeq)并且是逐个计算的,所以当 return 值被递归转换为字符串时,一切都会计算深度优先。
为了说明这一点 - 在 Clojure 和 CLJS 的 REPL 中尝试 (first (for [i (range 300)] (do (println "printing:" i) i)))
- 你将在 clojure 中打印 32 个数字,在 CLJS 中只打印一个数字。
如果你想更好地保证评估的顺序,你可以使用doseq
代替for
或者将for
包裹在doall
中。
希望这对您有所帮助。
旁注:就像@Josh,我最终在 Clojure 1.8 中没有得到 :f
,并且括号与 cljs 输出相同 - 这真的很奇怪......
我不确定我是否了解您目前想要如何使用 DFS 的结果。如果你想使用副作用,我。 e.打印所有节点到控制台,使用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))))))
这会将所有节点打印到控制台,深度优先。如果您想将遍历作为 return 值,请使用 for
但请确保您实际上 return 是一个有意义的值:
(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))
- 然后您可以将其展平以进行遍历。你也会得到懒惰的好处。
使用深度优先搜索的以下递归定义 Clojure (JVM) 和 ClojureScript(使用浏览器连接的 repl 和 lumo 进行测试)REPL 产生两个不同的输出,即打印节点的顺序不同,Clojure REPL产生重复的 :f
。 ClojureScript 命令是我所期望的行为。这是为什么?
代码:
(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
;; => ((() ()) (()) ())
Clojure 的 for
生成惰性序列。所有循环 dfs
调用的实际评估仅由您的 REPL 触发,因为它需要打印函数的输出,即 ((() ()) (()) ())
。如果你计算 (do (dfs graph :a) nil)
,你只会打印 :a
。
现在,Clojure 的惰性序列 evaluated in chunks of size 32 提高了效率。因此,当 REPL(通过 str
函数)评估第一个元素惰性序列的顶层 for
(应该打印 :b
)时,该序列的其他元素也会被评估,你在评估子节点的序列(也是惰性的)之前打印 :c
和 :e
。
相比之下,Clojurescript 的惰性序列不分块(LazySeq does not implement IChunkedSeq)并且是逐个计算的,所以当 return 值被递归转换为字符串时,一切都会计算深度优先。
为了说明这一点 - 在 Clojure 和 CLJS 的 REPL 中尝试 (first (for [i (range 300)] (do (println "printing:" i) i)))
- 你将在 clojure 中打印 32 个数字,在 CLJS 中只打印一个数字。
如果你想更好地保证评估的顺序,你可以使用doseq
代替for
或者将for
包裹在doall
中。
希望这对您有所帮助。
旁注:就像@Josh,我最终在 Clojure 1.8 中没有得到 :f
,并且括号与 cljs 输出相同 - 这真的很奇怪......
我不确定我是否了解您目前想要如何使用 DFS 的结果。如果你想使用副作用,我。 e.打印所有节点到控制台,使用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))))))
这会将所有节点打印到控制台,深度优先。如果您想将遍历作为 return 值,请使用 for
但请确保您实际上 return 是一个有意义的值:
(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))
- 然后您可以将其展平以进行遍历。你也会得到懒惰的好处。