Clojure程序在调试时工作正常,在repl中失败

时间:2018-06-12 15:46:33

标签: clojure core.async cider

我正在学习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)
()

我的猜测是,在第二种情况下出于某种原因,生产者在退出之前并没有等待消费者,但我不确定为什么。谢谢你的帮助!

3 个答案:

答案 0 :(得分:3)

问题是您对map的调用是懒惰的,并且在某些事情要求结果之前不会运行。您的代码中没有任何内容。

有两种解决方案:

(1)使用急切的功能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”对于人类读者来说是无用的。

此外,调试打印输出通常应该使用println,以便您可以看到每个打印输出的开始和结束位置。

答案 1 :(得分:1)

我要采取安全措施,这是由map的懒惰行为引起的,这条线正在产生副作用:

(map #(>!! in %1) s)

因为您从未明确使用结果,所以它永远不会运行。将其更改为使用mapv,这是严格的,或者更准确地说,使用doseq。切勿使用map来运行副作用。这意味着懒洋洋地改变一个列表,滥用它会导致这样的行为。

那为什么它在调试时有效呢?我猜测是因为调试器强迫评估作为其操作的一部分,它正在掩盖问题。

答案 2 :(得分:1)

正如你可以从docstring map读取的那样,返回一个懒惰的序列。我认为最好的方法是使用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