Clojure 程序在调试时工作正常,在 repl 中失败
Clojure program works fine when debugged, fails in repl
我正在学习core.async
并编写了一个简单的生产者消费者代码:
(ns webcrawler.parallel
(:require [clojure.core.async :as async
:refer [>! <! >!! <!! go chan buffer close! thread alts! alts!! timeout]]))
(defn consumer
[in out f]
(go (loop [request (<! in)]
(if (nil? request)
(close! out)
(do (print f)
(let [result (f request)]
(>! out result))
(recur (<! in)))))))
(defn make-consumer [in f]
(let [out (chan)]
(consumer in out f)
out))
(defn process
[f s no-of-consumers]
(let [in (chan (count s))
consumers (repeatedly no-of-consumers #(make-consumer in f))
out (async/merge consumers)]
(map #(>!! in %1) s)
(close! in)
(loop [result (<!! out)
results '()]
(if (nil? result)
results
(recur (<!! out)
(conj results result))))))
当我通过 Emacs 苹果酒提供的调试器中的 process
函数介入时,这段代码工作正常。
(process (partial + 1) '(1 2 3 4) 1)
(5 4 3 2)
但是,如果我 运行 它本身(或在调试器中点击继续),我得到一个空结果。
(process (partial + 1) '(1 2 3 4) 1)
()
我的猜测是,在第二种情况下,出于某种原因,生产者在退出之前不会等待消费者,但我不确定为什么。感谢您的帮助!
我敢打赌,这是由 map
的懒惰行为造成的,而这一行会产生副作用:
(map #(>!! in %1) s)
因为您从未明确使用结果,所以它永远不会 运行s。将其更改为使用 mapv
,这是严格的,或者更准确地说,使用 doseq
。永远不要使用 map
到 运行 的副作用。它旨在懒惰地转换列表,滥用它会导致这样的行为。
那么为什么它在调试时可以正常工作?我猜是因为调试器强制将评估作为其操作的一部分,这掩盖了问题。
问题是您对 map
的调用是惰性的,并且不会 运行 直到有人要求结果。在您的代码中没有这样做。
有2种解决方案:
(1) 使用eager函数mapv
:
(mapv #(>!! in %1) items)
(2) 使用 doseq
,它用于副作用操作(例如将值放在通道上):
(doseq [item items]
(>!! in item))
两者都可以工作并产生输出:
(process (partial + 1) [1 2 3 4] 1) => (5 4 3 2)
P.S。您在 (defn consumer ...)
中有一个调试语句
(print f)
在输出中产生大量噪音:
<#clojure.core$partial$fn__5561 #object[clojure.core$partial$fn__5561 0x31cced7
"clojure.core$partial$fn__5561@31cced7"]>
即背靠背重复5次。你可能想避免这种情况,因为打印功能 "refs" 对人类来说毫无用处 reader.
此外,调试打印输出一般应使用 println
,这样您就可以看到每个打印输出的开始和结束位置。
正如您可以从文档字符串中读取的那样 map
returns 一个惰性序列。而且我认为最好的方法是使用dorun
。这是来自 clojuredocs 的示例:
;;map a function which makes database calls over a vector of values
user=> (map #(db/insert :person {:name %}) ["Fred" "Ethel" "Lucy" "Ricardo"])
JdbcSQLException The object is already closed [90007-170] org.h2.message.DbE
xception.getJdbcSQLException (DbException.java:329)
;;database connection was closed before we got a chance to do our transactions
;;lets wrap it in dorun
user=> (dorun (map #(db/insert :person {:name %}) ["Fred" "Ethel" "Lucy" "Ricardo"]))
DEBUG :db insert into person values name = 'Fred'
DEBUG :db insert into person values name = 'Ethel'
DEBUG :db insert into person values name = 'Lucy'
DEBUG :db insert into person values name = 'Ricardo'
nil
我正在学习core.async
并编写了一个简单的生产者消费者代码:
(ns webcrawler.parallel
(:require [clojure.core.async :as async
:refer [>! <! >!! <!! go chan buffer close! thread alts! alts!! timeout]]))
(defn consumer
[in out f]
(go (loop [request (<! in)]
(if (nil? request)
(close! out)
(do (print f)
(let [result (f request)]
(>! out result))
(recur (<! in)))))))
(defn make-consumer [in f]
(let [out (chan)]
(consumer in out f)
out))
(defn process
[f s no-of-consumers]
(let [in (chan (count s))
consumers (repeatedly no-of-consumers #(make-consumer in f))
out (async/merge consumers)]
(map #(>!! in %1) s)
(close! in)
(loop [result (<!! out)
results '()]
(if (nil? result)
results
(recur (<!! out)
(conj results result))))))
当我通过 Emacs 苹果酒提供的调试器中的 process
函数介入时,这段代码工作正常。
(process (partial + 1) '(1 2 3 4) 1)
(5 4 3 2)
但是,如果我 运行 它本身(或在调试器中点击继续),我得到一个空结果。
(process (partial + 1) '(1 2 3 4) 1)
()
我的猜测是,在第二种情况下,出于某种原因,生产者在退出之前不会等待消费者,但我不确定为什么。感谢您的帮助!
我敢打赌,这是由 map
的懒惰行为造成的,而这一行会产生副作用:
(map #(>!! in %1) s)
因为您从未明确使用结果,所以它永远不会 运行s。将其更改为使用 mapv
,这是严格的,或者更准确地说,使用 doseq
。永远不要使用 map
到 运行 的副作用。它旨在懒惰地转换列表,滥用它会导致这样的行为。
那么为什么它在调试时可以正常工作?我猜是因为调试器强制将评估作为其操作的一部分,这掩盖了问题。
问题是您对 map
的调用是惰性的,并且不会 运行 直到有人要求结果。在您的代码中没有这样做。
有2种解决方案:
(1) 使用eager函数mapv
:
(mapv #(>!! in %1) items)
(2) 使用 doseq
,它用于副作用操作(例如将值放在通道上):
(doseq [item items]
(>!! in item))
两者都可以工作并产生输出:
(process (partial + 1) [1 2 3 4] 1) => (5 4 3 2)
P.S。您在 (defn consumer ...)
(print f)
在输出中产生大量噪音:
<#clojure.core$partial$fn__5561 #object[clojure.core$partial$fn__5561 0x31cced7
"clojure.core$partial$fn__5561@31cced7"]>
即背靠背重复5次。你可能想避免这种情况,因为打印功能 "refs" 对人类来说毫无用处 reader.
此外,调试打印输出一般应使用 println
,这样您就可以看到每个打印输出的开始和结束位置。
正如您可以从文档字符串中读取的那样 map
returns 一个惰性序列。而且我认为最好的方法是使用dorun
。这是来自 clojuredocs 的示例:
;;map a function which makes database calls over a vector of values
user=> (map #(db/insert :person {:name %}) ["Fred" "Ethel" "Lucy" "Ricardo"])
JdbcSQLException The object is already closed [90007-170] org.h2.message.DbE
xception.getJdbcSQLException (DbException.java:329)
;;database connection was closed before we got a chance to do our transactions
;;lets wrap it in dorun
user=> (dorun (map #(db/insert :person {:name %}) ["Fred" "Ethel" "Lucy" "Ricardo"]))
DEBUG :db insert into person values name = 'Fred'
DEBUG :db insert into person values name = 'Ethel'
DEBUG :db insert into person values name = 'Lucy'
DEBUG :db insert into person values name = 'Ricardo'
nil