【问题标题】:Clojure program works fine when debugged, fails in replClojure程序在调试时工作正常,在repl中失败
【发布时间】:2018-06-12 15:46:32
【问题描述】:

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

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

【问题讨论】:

    标签: clojure core.async cider


    【解决方案1】:

    问题是您对map 的调用是懒惰的,并且在某些要求结果之前不会运行。在您的代码中没有这样做。

    有两种解决方案:

    (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)
    

    附:你在(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,这样您就可以看到每个打印输出的开始和结束位置。

    【讨论】:

      【解决方案2】:

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

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

      因为您从不明确使用结果,所以它永远不会运行。将其更改为使用mapv,这是严格的,或者更准确地说,使用doseq。永远不要使用map 来运行副作用。它的目的是懒惰地转换一个列表,滥用它会导致这样的行为。

      那么为什么它在调试时工作?我猜是因为调试器强制评估作为其操作的一部分,这掩盖了问题。

      【讨论】:

        【解决方案3】:

        正如您从文档字符串中看到的那样,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
        

        【讨论】:

          猜你喜欢
          • 2023-03-20
          • 2017-03-01
          • 2015-02-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-11-29
          相关资源
          最近更新 更多