Программа Clojure работает нормально при отладке, не работает в Repl - PullRequest
0 голосов
/ 12 июня 2018

Я изучаю 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.

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

Однако, если я запускаю его сам (или нажимаю продолжить в отладчике), я получаю пустой результат.

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

Я предполагаю, что во втором случае для некоторыхПричина, по которой производитель не ждет потребителей перед выходом, но я не знаю почему.Спасибо за помощь!

Ответы [ 3 ]

0 голосов
/ 12 июня 2018

Проблема в том, что ваш звонок на map ленив и не будет выполняться, пока что-то не попросит результатов.Ничто не делает это в вашем коде.

Существует 2 решения:

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

PS У вас есть оператор отладки в (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, чтобы вы могли видеть, где начинается и заканчивается каждый из них.

0 голосов
/ 13 июня 2018

Как вы можете прочитать из строки документации 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
0 голосов
/ 12 июня 2018

Я собираюсь сделать безопасный удар, что это вызвано ленивым поведением map, и этой строкой, которая выполняет побочные эффекты:

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

Поскольку вы никогда явно не используетерезультаты, это никогда не работает.Измените его на mapv, что строгое, или, что более правильно, используйте doseq.Никогда не используйте map для запуска побочных эффектов.Он предназначен для ленивого преобразования списка, и злоупотребление им приводит к такому поведению.

Так почему же это работает во время отладки?Я собираюсь угадать, потому что отладчик форсирует оценку как часть своей операции, которая маскирует проблему.

...