代码之家  ›  专栏  ›  技术社区  ›  radious

clojure程序在调试时运行良好,在repl中失败

  •  0
  • radious  · 技术社区  · 8 年前

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

    当我通过 process Emacs的cider提供的调试器中的函数。

    (process (partial + 1) '(1 2 3 4) 1)
    (5 4 3 2)
    

    但是,如果我自己运行它(或者在调试器中单击continue),则会得到一个空结果。

    (process (partial + 1) '(1 2 3 4) 1)
    ()
    

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

    3 回复  |  直到 8 年前
        1
  •  3
  •   Alan Thompson    8 年前

    问题是你打电话给 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)
    

    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 所以你可以看到每一个开始和结束。

        2
  •  1
  •   Carcigenicate    8 年前

    我敢肯定这是由于 map ,这条线有副作用:

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

    因为您从未显式地使用结果,所以它从不运行。更改为使用 mapv ,这是严格的,或者更正确地使用 doseq . 从不使用 地图 以产生副作用。这意味着懒惰地改变一个列表,滥用它会导致这样的行为。

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

        3
  •  1
  •   K. Cherkashin    8 年前

    从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
    
    推荐文章