Очищение карты его каналов

Предположим, у нас есть карта 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))))

В основном, что здесь делается:

  1. Каждая запись на карте преобразуется в канал, который будет содержать эту запись на карте. Если значение записи карты является каналом, оно извлекается внутри go-блок в функции отображения.
  2. Каналы с картами-записями mergeв одном. После этого шага у вас есть канал с коллекцией записей на карте.
  3. Канал с записями на карте преобразуется в канал, который будет содержать нужную карту (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"}

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