apply强制给定一个惰性序列来实现四个元素。
(take 1
(apply concat
(repeatedly #(do
(println "called")
(range 1 10)))))
=> "called"
=> "called"
=> "called"
=> "called"
有没有一种方法可以避免这种行为?
谢谢
答案 0 :(得分:3)
有没有办法做这样的行为
apply
?
我认为简短的答案是:并非不重新实现Clojure的一些基本功能。 apply
的实现直接依赖于Clojure的可调用函数的实现,并通过枚举参数的输入序列来尝试发现给定函数对.invoke
的正确性。
使用函数而不是无分块的序列/化简/换能器,而不是使用带有apply
的可变函数来分解您的解决方案可能更容易。例如,这是使用换能器重新实现的示例,它仅调用一次body函数(每range
的长度):
(sequence
(comp
(mapcat identity)
(take 1))
(repeatedly #(do
(println "called")
(range 1 10))))
;; called
;; => (1)
使用apply
,concat
,seq
,LazySeq
等查看示例中的情况:
repeatedly
返回一个新的LazySeq
实例:(lazy-seq (cons (f) (repeatedly f)))
。(apply concat <args>)
,apply
调用其参数列表上的RT.seq
,对于LazySeq
,它调用LazySeq.seq
,后者将调用您的功能apply
然后调用Java impl。方法applyToHelper
尝试获取参数序列的长度。 applyToHelper
尝试使用RT.boundedLength
确定参数列表的长度,该内部调用next
并依次调用seq
,因此它可以找到要调用的proper overload of IFn.invoke
concat
本身又增加了lazy-seq
行为的另一层。您可以看到这些调用的堆栈跟踪,如下所示:
(take 1
(repeatedly #(do
(clojure.stacktrace/print-stack-trace (Exception.))
(range 1 10))))
第一条轨迹来自apply
最初对seq
的调用,随后的轨迹来自RT.boundedLength
。
答案 1 :(得分:0)
实际上,您的代码无法实现串联集合中的任何项(在您的情况下为范围)。因此,就元素而言,生成的集合确实是惰性的。您从函数调用中获得的打印件会生成未实现的延迟序列。可以很容易地用这种方法检查这一点:
(defn range-logged [a b]
(lazy-seq
(when (< a b)
(println "realizing item" a)
(cons a (range-logged (inc a) b)))))
user> (take 1
(apply concat
(repeatedly #(do
(println "called")
(range-logged 1 10)))))
;;=> called
;; called
;; called
;; called
;; realizing item 1
(1)
user> (take 10
(apply concat
(repeatedly #(do
(println "called")
(range-logged 1 10)))))
;; called
;; called
;; called
;; called
;; realizing item 1
;; realizing item 2
;; realizing item 3
;; realizing item 4
;; realizing item 5
;; realizing item 6
;; realizing item 7
;; realizing item 8
;; realizing item 9
;; realizing item 1
(1 2 3 4 5 6 7 8 9 1)
所以我的猜测是,只要repeatedly
闭包返回的集合是惰性的,就不必担心