It seems that "apply" realizes one more element than is required to a)
bind all non-rest arguments and b) determine/validate the arity, in
the case that the function has a rest argument.

user=> (defn report-seq [] (iterate (fn [n] (println n) (inc n)) 0))
#'user/report-seq
user=> (doall (take 3 (report-seq)))
0
1
(0 1 2)

As you can see, this sequence's realization has side effects for all
but the first element. Realizing each number after that prints the
number that is one lower.

user=> (defn test1 [& xs])
#'user/test1

This function has a single rest argument and never uses it.

user=> (apply test1 (report-seq))
0
nil

As you can see, apply causes it to call the iterate function once, so
it's realizing both the 0 and the 1 at the start of the sequence. In
theory it doesn't need to realize any.

user=> (defn test2 [x & xs])
#'user/test2

Has a single argument before the rest argument.

user=> (apply test2 (report-seq))
0
1
nil

Apply with this one realizes three elements.

user=> (defn test3 [x1 x2 & xs])
#'user/test3
user=> (apply test3 (report-seq))
0
1
2
nil

And now four. It seems, in general, to look two elements ahead of
where it needs to.

Even worse, if given a function without a rest argument and a too-long
sequence, it doesn't just halt and generate an arity exception as soon
as it discovers 1 more element in the sequence than the maximum arity
of the function:

user=> (defn test4 [x1 x2])
#'user/test4
user=> (apply test4 (take 10 (report-seq)))
0
1
2
3
4
5
6
7
8
#<CompilerException java.lang.IllegalArgumentException: Wrong number
of args (10) passed to: user$eval273$test4 (NO_SOURCE_FILE:0)>

It realizes the entire seq! The best case would have been if it had printed

user=> (defn test4 [x1 x2])
#'user/test4
user=> (apply test4 (take 10 (report-seq)))
0
1
#<CompilerException java.lang.IllegalArgumentException: Wrong number
of args (10) passed to: user$eval273$test4 (NO_SOURCE_FILE:0)>

i.e. realized the 0, then the 1 (printing 0), then the 2 (printing 1)
as it checked whether there were any more elements in the sequence.

Is there a solid reason for this behavior of apply? If you ask me,
when the function has rest arguments, it should realize only as much
of the sequence as is needed to bind the non-rest arguments and bind
the rest argument to (nthrest x the-input-seq), realizing two fewer
elements than the current implementation; or if it's desired that the
rest argument be bound to nil if that's empty, to (nthnext x
the-input-seq), realizing one fewer. And when the function does not,
it should *never* realize more than one more than however many
elements are in the longest of the function's arglists -- one more in
the case that the sequence is longer than the longest arglist, to
discover that fact and then throw an exception.

Unless, of course, there's a very good reason (efficiency?) for the
current behavior (two-element lookahead with rest args, and apparently
calling doall or count on the sequence without).

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to