Могу ли я пошагово обработать нереализованный lazy-seq

У меня есть lazy-seq, где каждый элемент требует времени для вычисления:

(defn gen-lazy-seq [size]
  (for [i (range size)]
    (do
      (Thread/sleep 1000)
      (rand-int 10))))

Можно ли поэтапно оценить эту последовательность и распечатать результаты. Когда я пытаюсь обработать его с помощью for или doseq, clojure всегда осознает всю ленивую последовательность перед выводом чего-либо:

(doseq [item (gen-lazy-seq 10)]
  (println item))

(for [item (gen-lazy-seq 10)]
  (println item))

Оба выражения будут ждать 10 секунд, прежде чем что-либо распечатать. Я рассматривал doall и dorun как решение, но они требуют, чтобы функция создания lazy-seq содержала println. Я хотел бы определить функцию создания lazy-seq и функцию печати lazy-seq отдельно и заставить их работать вместе, элемент за элементом.

Мотивация для попытки сделать это: у меня есть сообщения, приходящие по сети, и я хочу начать их обработку до того, как все будут получены. При этом все сообщения, соответствующие запросу, неплохо было бы сохранять в lazy-seq.

Изменить 1:

Ответ JohnJ показывает, как создать lazy-seq, который будет оцениваться шаг за шагом. Я хотел бы знать, как шаг за шагом оценить любой lazy-seq.

Я смущен, потому что запуск (chunked-seq? (gen-lazy-seq 10)) на gen-lazy-seq, как определено выше, ИЛИ, как определено в ответе JohnJ, оба возвращают false. Итак, проблема не может быть в том, что один создает последовательность фрагментов, а другой - нет.

В этом ответе показана функция seq1, которая превращает фрагментированный lazy-seq в фрагментарный. Использование этой функции по-прежнему вызывает ту же проблему с задержкой вывода. Я подумал, что, возможно, задержка связана с какой-то буферизацией в ответе, поэтому я попытался также распечатать время, когда реализуется каждый элемент в seq:

(defn seq1 [s]
  (lazy-seq
    (when-let [[x] (seq s)]
      (cons x (seq1 (rest s))))))

(let [start-time (java.lang.System/currentTimeMillis)]
  (doseq [item (seq1 (gen-lazy-seq 10))]
    (let [elapsed-time (- (java.lang.System/currentTimeMillis) start-time)]
      (println "time: " elapsed-time "item: " item))))

; output:
time:  10002 item:  1
time:  10002 item:  8
time:  10003 item:  9
time:  10003 item:  1
time:  10003 item:  7
time:  10003 item:  2
time:  10004 item:  0
time:  10004 item:  3
time:  10004 item:  5
time:  10004 item:  0

То же самое с версией gen-lazy-seq от JohnJ работает должным образом.

; output:
time:  1002 item:  4
time:  2002 item:  1
time:  3002 item:  6
time:  4002 item:  8
time:  5002 item:  8
time:  6002 item:  4
time:  7002 item:  5
time:  8002 item:  6
time:  9003 item:  1
time:  10003 item:  4

Изменить 2:

Эта проблема возникает не только с последовательностями, сгенерированными с помощью. Эта последовательность, сгенерированная с помощью map, не может быть обработана пошагово независимо от упаковки seq1:

(defn gen-lazy-seq [size]
  (map (fn [_] 
         (Thread/sleep 1000)
         (rand-int 10))
       (range 0 size)))

Но эта последовательность, также созданная с помощью карты, работает:

(defn gen-lazy-seq [size] 
  (map (fn [_] 
         (Thread/sleep 1000)
         (rand-int 10))
       (repeat size :ignored)))

person snowape    schedule 26.06.2013    source источник
comment
Я также пробовал seq1 и не смог заставить его работать с Clojure 1.5 или с 1.2.1. Возможно, вы столкнулись с особенностями реализации макроса for, который не является прозрачным и который вы не можете просто отключить, заключив его в свой собственный ленивый seq.   -  person JohnJ    schedule 28.06.2013
comment
Спасибо за тестирование. Кажется разумным, что проблема связана с for. Я попытался создать lazy-seq с картой, и это сработало, как ожидалось.   -  person snowape    schedule 28.06.2013
comment
Тестируя еще немного, кажется, что у меня такая же проблема с последовательностями, сгенерированными картой.   -  person snowape    schedule 28.06.2013


Ответы (1)


Ленивые последовательности Clojure часто разбиты на части. Вы можете увидеть, как работает фрагмент в своем примере, если вы возьмете большие sizes (в этом случае будет полезно уменьшить время ожидания потока). См. Также эти связанные SO posts.

Хотя for кажется разбитым, следующее не работает и работает должным образом:

(defn gen-lazy-seq [size]
  (take size (repeatedly #(do (Thread/sleep 1000)
                              (rand-int 10)))))

(doseq [item (gen-lazy-seq 10)]
  (println item)) 

«У меня есть сообщения, поступающие по сети, и я хочу начать их обработку до того, как все будут получены». Разделенные или нет, но это действительно так, если вы обрабатываете их лениво.

person JohnJ    schedule 26.06.2013