问题
It appears that apply forces the realization of four elements given a lazy sequence.
(take 1
(apply concat
(repeatedly #(do
(println "called")
(range 1 10)))))
=> "called"
=> "called"
=> "called"
=> "called"
Is there a way to do an apply which does not behave this way?
Thank You
回答1:
Is there a way to do an
apply
which does not behave this way?
I think the short answer is: not without reimplementing some of Clojure's basic functionality. apply
's implementation relies directly on Clojure's implementation of callable functions, and tries to discover the proper arity of the given function to .invoke
by enumerating the input sequence of arguments.
It may be easier to factor your solution using functions over lazy, un-chunked sequences / reducers / transducers, rather than using variadic functions with apply
. For example, here's your sample reimplemented with transducers and it only invokes the body function once (per length of range
):
(sequence
(comp
(mapcat identity)
(take 1))
(repeatedly #(do
(println "called")
(range 1 10))))
;; called
;; => (1)
Digging into what's happening in your example with apply
, concat
, seq
, LazySeq
, etc.:
repeatedly
returns a newLazySeq
instance:(lazy-seq (cons (f) (repeatedly f)))
.- For the given 2-arity
(apply concat <args>)
,apply
callsRT.seq
on its argument list, which for aLazySeq
then invokes LazySeq.seq, which will invoke your function apply
then calls a Java impl. method applyToHelper which tries to get the length of the argument sequence.applyToHelper
tries to determine the length of the argument list usingRT.boundedLength
, which internally callsnext
and in turnseq
, so it can find the proper overload of IFn.invoke to callconcat
itself adds another layer oflazy-seq
behavior.
You can see the stack traces of these invocations like this:
(take 1
(repeatedly #(do
(clojure.stacktrace/print-stack-trace (Exception.))
(range 1 10))))
The first trace descends from the apply
's initial call to seq
, and the subsequent traces from RT.boundedLength
.
回答2:
in fact, your code doesn't realize any of the items from the concatenated collections (ranges in your case). So the resulting collection is truly lazy as far as elements are concerned. The prints you get are from the function calls, generating unrealized lazy seqs. This one could easily be checked this way:
(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)
So my guess is that you have nothing to worry about, as long as the collection returned from repeatedly
closure is lazy
来源:https://stackoverflow.com/questions/51959298/clojure-apply-that-does-not-realize-the-first-four-elements-of-a-lazy-sequence