假设我有一个函数(get-events "feed")
,它按时间顺序返回一个事件向量,取自外部源。
现在,在任何给定时刻,该函数都会返回截至该时间点的事件列表。几秒钟后,当饲料不断增长时,它会再返回一些事件等。
如果我想创建一个永远从feed中提取新事件的lazy-seq,确保它不会重复那些已经看过的事件,我该怎么写呢?当我不使用recur
时,我遇到堆栈溢出错误,但我不能使用recur,因为它不会出现在尾部位置。
(def continually-list-events
([feed] (continually-list-events feed (hash-set)))
([feed seen]
(let [events-now (get-events feed)]
(into (remove seen events-now)
(lazy-seq
(continually-list-events feed
(into seen events-now))))))
你可以看到我正在尝试使用累加器来跟踪已经看过的事件(在一组中),并且我确保总是过滤掉我见过的那些事件。
答案 0 :(得分:2)
如果每个步骤都跟踪到目前为止已收到的事件数,那么该迭代可以通过删除旧事件来返回一系列新事件。
user> (->> (iterate (fn [[events-so-far contents]]
(let [events (get-events)
new-events (drop events-so-far events)]
[(count events) new-events])))
(mapcat second))
然后,您可以从序列中删除计数,并将事件块整合为一系列单个事件。
在您的示例中,stackoverflow是因为在调用cons
之后没有调用lazy-seq
所以它正在计算整个列表为序列中的第一项。
user> (defn example [x] (lazy-seq (cons x (example (inc x)))))
#'user/example
user> (take 5 (example 4))
(4 5 6 7 8)
user> (defn example [x] (lazy-seq (example (inc x))))
#'user/example
user> (take 5 (example 4))
... long pause then out of memory ...
PS:直接使用lazy-seq有点不常见,但知道它是如何工作的很重要。