Очищение карты его каналов
Предположим, у нас есть карта m
со следующей структурой:
{:a (go "a")
:b "b"
:c "c"
:d (go "d")}
Как показано, m
имеет четыре ключа, два из которых содержат каналы.
Вопрос: Как можно написать общую функцию (или макрос?) cleanse-map
которая берет карту как m
и выводит свою безканальную версию (которая в этом случае будет {:a "a" :b "b" :c "c" :d "d"}
)?
Хорошая вспомогательная функция для этого вопроса может быть следующей:
(defn chan? [c]
(= (type (chan)) (type c)))
Также не имеет значения, если возвращаемое значение cleanse-map
(или как там это называется) сам по себе канал. то есть:
`(cleanse-map m) ;=> (go {:a "a" :b "b" :c "c" :d "d"})
2 ответа
Решение
Ограничения core.async
сделать реализацию cleanse-map
не так просто. Но должно работать следующее:
(defn cleanse-map [m]
(let [entry-chs (map
(fn [[k v]]
(a/go
(if (chan? v)
[k (a/<! v)]
[k v])))
m)]
(a/into {} (a/merge entry-chs))))
В основном, что здесь делается:
- Каждая запись на карте преобразуется в канал, который будет содержать эту запись на карте. Если значение записи карты является каналом, оно извлекается внутри
go
-блок в функции отображения. - Каналы с картами-записями
merge
в одном. После этого шага у вас есть канал с коллекцией записей на карте. - Канал с записями на карте преобразуется в канал, который будет содержать нужную карту (
a/into
шаг).
(ns foo.bar
(:require
[clojure.core.async :refer [go go-loop <!]]
[clojure.core.async.impl.protocols :as p]))
(def m
{:a (go "a")
:b "b"
:c "c"
:d (go "d")
:e "e"
:f "f"
:g "g"
:h "h"
:i "i"
:j "j"
:k "k"
:l "l"
:m "m"})
(defn readable? [x]
(satisfies? p/ReadPort x))
(defn cleanse-map
"Takes from each channel value in m,
returns a single channel which will supply the fully realized m."
[m]
(go-loop [acc {}
[[k v :as kv] & remaining] (seq m)]
(if kv
(recur (assoc acc k (if (readable? v) (<! v) v)) remaining)
acc)))
(go (prn "***" (<! (cleanse-map m))))
=> "***" {:m "m",:e "e",:l "l",:k "k",:g "g",:c "c",:j "j",:h "h",:b "b",:d "d",:f "f",:i "i",:a "a"}