Превратите этот вызов Clojure в ленивую последовательность - PullRequest
2 голосов
/ 27 октября 2009

Я работаю с инструментарием обмена сообщениями (это бывает Распространение , но я не знаю, насколько важны детали). Для получения сообщений из этого инструментария требуется некоторый шаблон:

  1. Создать соединение с демоном.
  2. Вступить в группу.
  3. Получите одно или несколько сообщений.
  4. Покинуть группу.
  5. Отключиться от демона.

Следуя некоторым идиомам, которые я видел, использовал в другом месте , я смог подготовить некоторые рабочие функции, используя 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. Грр. Также я пропустил некоторые макросы, которые здесь не относятся к структурному вопросу. )

Это работает достаточно хорошо. Я могу позвонить получать сообщение изнутри структуры, как:

(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 сверху: выход из группы и отключение от демона. Как связать состояние соединения и группы в последовательности и , выполнить необходимый код очистки, когда последовательность больше не нужна?

Ответы [ 2 ]

6 голосов
/ 27 октября 2009

В этой статье описывается, как именно это сделать, используя clojure-contrib fill-queue. Относительно очистки - удобная вещь в fill-queue заключается в том, что вы можете предоставить блокирующую функцию, которая очищает себя в случае ошибки или какого-либо условия. Вы также можете держать ссылку на ресурс, чтобы управлять им извне. Последовательность просто прекратится. Таким образом, в зависимости от вашего семантического требования, вам придется выбрать подходящую стратегию.

3 голосов
/ 28 октября 2009

Попробуйте это:

(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) также будут завершать список.

...