通过连接集合创建惰性序列

Create Lazy Sequency by Concatenating Collections

通过连接集合创建惰性序列。

考虑以下函数:

(defn req []
  (Thread/sleep 1000)
  (repeat 4 (rand-int 10)))

添加睡眠是因为该函数最终将是一个 http 请求,因此它应该模拟延迟。

示例输出:

(req)
;; (8 8 8 8)

(req)
;; (4 4 4 4)

我现在正在考虑一个函数,它通过串联后续 req 结果来创建惰性序列构建。

(take 10 (f req))
;; (3 3 3 3 2 2 2 2 9 9)

这是一种实现方式:

(defn f [g]
  (lazy-seq (concat (g) (f g))))

这是要走的路吗?我以某种方式猜测可能已经有一个可用的抽象。我试过 lazy-cat,但这个宏似乎只适用于固定数量的给定序列。


事实证明这是一个工作抽象:

(take 10 (apply concat (repeatedly req)))

然而,惰性序列的分块看起来会导致 req 被调用的次数超过此处所需的次数,如果是 http 请求,这是不可接受的。

惰性序列元素的 "unneeded" 实现正在发生,因为 apply needs to know 应用传递函数的参数数量。

快速浏览一下 Clojure 核心库,它似乎没有提供连接一系列序列的功能,同时以您想要的方式处理惰性(不冗余实现传递的惰性序列的元素),所以,你需要自己实现它。

以下是可能的解决方案:

(defn apply-concat [xs]
  (lazy-seq
   (when-let [s (seq xs)]
     (concat (first s) (apply-concat (rest s))))))

然后:

user> (defn req []
        (println "--> making request")
        [1 2 3 4])
#'user/req
user> (dorun (take 4 (apply-concat (repeatedly req))))
--> making request
nil
user> (dorun (take 5 (apply-concat (repeatedly req))))
--> making request
--> making request
nil
user> (dorun (take 8 (apply-concat (repeatedly req))))
--> making request
--> making request
nil
user> (dorun (take 9 (apply-concat (repeatedly req))))
--> making request
--> making request
--> making request
nil

这种方法唯一的问题是炸毁堆栈的危险,因为 apply-concat 可能会无限递归。

更新:

准确的说apply实现了传递惰性序列的(arity of passed function + 1)个元素:

user> (dorun (take 1 (apply (fn [& xs] xs) (repeatedly req))))
--> making request
--> making request
nil
user> (dorun (take 1 (apply (fn [x & xs] xs) (repeatedly req))))
--> making request
--> making request
--> making request
nil
user> (dorun (take 1 (apply (fn [x y & xs] xs) (repeatedly req))))
--> making request
--> making request
--> making request
--> making request
nil

怎么样

(take 14 
  (mapcat identity (repeatedly req)))

说明:

(defn req [] 
  (print ".") 
  (repeat 4 (rand-int 10)))

(def x 
  (take 80 (mapcat identity (repeatedly req))))
; prints .... = 4x ; this is probably some repl eagerness

; to take 80 items, 20 realizatons (of 4 items) are requrend 
(def y 
  (doall
    (take 80 (mapcat identity (repeatedly req))))) 
 ; prints ..................... = 21x 

编辑:关于这 4 个早期实现:

我认为这是由于 apply,我们 mapcat 使用了它。 它最多实现 4 个参数 [^clojure.lang.IFn f a b c d & args] 给定多个参数。