Невозможно использовать для цикла в блоке 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-ад в других языках.

Другие вопросы по тегам