Превратите этот вызов Clojure в ленивую последовательность
-
06-07-2019 - |
Вопрос
Я работаю с набором инструментов для обмена сообщениями (это распространение , но я этого не знаю детали имеют значение). Для получения сообщений из этого инструментария необходим некоторый шаблон:
<Ол>Следуя некоторым идиомам, которые я видел, использовал в другом месте , я смог приготовить некоторые рабочие функции, использующие Java API Spread и формы взаимодействия Clojure:
(defn connect-to-daemon
"Open a connection"
[daemon-spec]
(let [connection (SpreadConnection.)
{:keys [host port user]} daemon-spec]
(doto connection
(.connect (InetAddress/getByName host) port user false false))))
(defn join-group
"Join a group on a connection"
[cxn group-name]
(doto (SpreadGroup.)
(.join cxn group-name)))
(defn with-daemon*
"Execute a function with a connection to the specified daemon"
[daemon-spec func]
(let [daemon (merge *spread-daemon* daemon-spec)
cxn (connect-to-daemon daemon-spec)]
(try
(binding [*spread-daemon* (assoc daemon :connection cxn)]
(func))
(finally
(.disconnect cxn)))))
(defn with-group*
"Execute a function while joined to a group"
[group-name func]
(let [cxn (:connection *spread-daemon*)
grp (join-group cxn group-name)]
(try
(binding [*spread-group* grp]
(func))
(finally
(.leave grp)))))
(defn receive-message
"Receive a single message. If none are available, this will block indefinitely."
[]
(let [cxn (:connection *spread-daemon*)]
(.receive cxn)))
(в основном та же идиома, что и with-open
, просто класс SpreadConnection
использует disconnect
вместо close
. Grr. Также я пропустил некоторые макросы, которые не имеют отношения к структурному вопросу здесь.)
Это работает достаточно хорошо. Я могу вызвать Receive-сообщение из структуры, например:
(with-daemon {:host "localhost" :port 4803}
(with-group "aGroup"
(... looping ...
(let [msg (receive-message)]
...))))
Мне пришло в голову, что receive-message
было бы удобнее использовать, если бы это была бесконечная ленивая последовательность, которая создает сообщения. Итак, если я хочу присоединиться к группе и получать сообщения, код вызова должен выглядеть примерно так:
(def message-seq (messages-from {:host "localhost" :port 4803} "aGroup"))
(take 5 message-seq)
Я видел множество примеров ленивых последовательностей без очистки, это не так уж сложно. Подвох - шаги 4 и 5 сверху: выход из группы и отключение от демона. Как связать состояние соединения и группы в последовательность и запустить необходимый код очистки, когда последовательность больше не нужна?
Решение
Эта статья описывает как это сделать, используя clojure-contrib fill-queue. Относительно очистки - полезная вещь в fill-queue заключается в том, что вы можете предоставить блокирующую функцию, которая очищает себя в случае ошибки или какого-либо условия. Вы также можете держать ссылку на ресурс, чтобы управлять им извне. Последовательность просто прекратится. Таким образом, в зависимости от вашего семантического требования, вам придется выбрать подходящую стратегию.
Другие советы
Попробуйте это:
(ns your-namespace
(:use clojure.contrib.seq-utils))
(defn messages-from [daemon-spec group-name]
(let [cnx (connect-to-deamon daemon-spec))
group (connect-to-group cnx group-name)]
(fill-queue (fn [fill]
(if done?
(do
(.leave group)
(.disconnect cnx)
(throw (RuntimeException. "Finished messages"))
(fill (.receive cnx))))))
Установить готово? true, когда вы хотите завершить список. Кроме того, любые исключения (.receive cnx) также будут завершать список.