Невозможно использовать для цикла в блоке go из core.async?
Я плохо знаком с библиотекой core.async и пытаюсь понять это с помощью эксперимента.
Но когда я попробовал:
(let [i (async/chan)] (async/go (doall (for [r [1 2 3]] (async/>! i r)))))
это дает мне очень странное исключение:
CompilerException java.lang.IllegalArgumentException: No method in multimethod '-item-to-ssa' for dispatch value: :fn
и я попробовал другой код:
(let [i (async/chan)] (async/go (doseq [r [1 2 3]] (async/>! i r))))
это не имеет никакого исключения компилятора вообще.
Я полностью сбит с толку. Что случилось?
1 ответ
Таким образом, Go-блок Clojure останавливает трансляцию на границах функций по многим причинам, но самая большая из них - это простота. Это чаще всего наблюдается при построении ленивых последовательностей:
(go (lazy-seq (<! c)))
Собирается во что-то вроде этого:
(go (clojure.lang.LazySeq. (fn [] (<! c))))
Теперь давайте подумаем об этом очень быстро... что это должно вернуть? Предполагая, что вы, вероятно, хотели, был ленивый seq, содержащий значение, взятое из c, но <!
Необходимо преобразовать оставшийся код функции в обратный вызов, но LazySeq ожидает, что функция будет синхронной. Там действительно нет способа обойти это ограничение.
Итак, вернемся к вашему вопросу, если вы макроэкспандировать for
вы увидите, что он на самом деле не зацикливается, а расширяется в кучу кода, который в конечном итоге вызывает lazy-seq
и поэтому парковочные операции не работают внутри тела. doseq
(а также dotimes
) однако поддерживаются loop
/recur
и так они будут работать отлично.
Есть несколько других мест, где это может сбить вас с толку with-bindings
быть одним примером. По сути, если макрос вставляет ваши операции парковки core.async во вложенную функцию, вы получите эту ошибку.
Тогда я предлагаю сохранить тело ваших ходовых блоков как можно более простым. Напишите чистые функции, а затем обработайте тело блоков go как места для выполнения ввода-вывода.
------------ РЕДАКТИРОВАТЬ -------------
Под остановкой трансляции на границах функций я имею в виду следующее: блок go берет свое тело и переводит его в конечный автомат. Каждый звонок <!
>!
или же alts!
(и несколько других) считаются переходами конечного автомата, в которых выполнение блока может приостанавливаться. В каждой из этих точек машина превращается в обратный вызов и подключается к каналу. Когда этот макрос достигает fn
форма перестает переводить. Так что вы можете звонить только <!
изнутри блока go, а не внутри функции внутри блока кода.
Это часть волшебства core.async. Без макроса go код core.async очень похож на callback-ад в других языках.