What is this function?

254 views
Skip to first unread message

Larry Travis

unread,
Oct 9, 2012, 1:03:14 PM10/9/12
to clo...@googlegroups.com, Larry Travis
As participants in this googlegroup have often observed, an excellent way to learn Clojure is to study the source definitions of its API functions.   This advice works better for learners who know Java, which I don't. I sometimes think that, if I were to teach a "Clojure Programming" course, I would list a "Java Programming" course as a prerequisite (for both teacher and students!). But here is some Clojure.core source code that raises a problem for me which surely doesn't have anything to do with not knowing Java.

user> (source list*)
(defn list*
  "Creates a new list containing the items prepended to the rest, the
  last of which will be treated as a sequence."
  {:added "1.0"
   :static true}
  ([args] (seq args))
  ([a args] (cons a args))
  ([a b args] (cons a (cons b args)))
  ([a b c args] (cons a (cons b (cons c args))))
  ([a b c d & more]
     (cons a (cons b (cons c (cons d (spread more)))))))

--------------
What is the function spread?  It is not in the Clojure API -- and the definition of list* seems to me to be complete without it.
  --Larry

Mark Rathwell

unread,
Oct 9, 2012, 1:12:56 PM10/9/12
to clo...@googlegroups.com
https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L588
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@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+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

Andy Fingerhut

unread,
Oct 9, 2012, 1:20:05 PM10/9/12
to clo...@googlegroups.com, Larry Travis
It is not in the Clojure API because it is declared private in the source file core.clj where it is defined. If you want to study Clojure's core code, it is essential in cases like this that you get a full source tree and search through it for such things:

git clone git://github.com/clojure/clojure.git

Or any other method you like to get it from here:

http://github.com/clojure/clojure

You can also see it from the REPL with:

(source clojure.core/spread)

Andy

Tassilo Horn

unread,
Oct 9, 2012, 1:33:54 PM10/9/12
to clo...@googlegroups.com, Larry Travis
Larry Travis <tra...@cs.wisc.edu> writes:

> user> (source list*)
> (defn list*
> "Creates a new list containing the items prepended to the rest, the
> last of which will be treated as a sequence."
> {:added "1.0"
> :static true}
> ([args] (seq args))
> ([a args] (cons a args))
> ([a b args] (cons a (cons b args)))
> ([a b c args] (cons a (cons b (cons c args))))
> ([a b c d & more]
> (cons a (cons b (cons c (cons d (spread more)))))))
>
> --------------
> What is the function _spread_? It is not in the Clojure API

It's a private function defined in clojure/core.clj directly above
list*.

--8<---------------cut here---------------start------------->8---
(defn spread
{:private true
:static true}
[arglist]
(cond
(nil? arglist) nil
(nil? (next arglist)) (seq (first arglist))
:else (cons (first arglist) (spread (next arglist)))))
--8<---------------cut here---------------end--------------->8---

> and the definition of _list*_ seems to me to be complete without it.

No. It makes

(list* 1 2 3 4 5 [6 7] [8 9]) => (1 2 3 4 5 [6 7] 8 9)

else it would result in (1 2 3 4 5 [6 7] [8 9]), i.e., it implements the
special meaning of list*'s last parameter.

Bye,
Tassilo

Larry Travis

unread,
Oct 9, 2012, 6:39:40 PM10/9/12
to clo...@googlegroups.com, Larry Travis
Mark, Andy, Tassilo:
Very helpful, gentlemen. You are kind and skillful tutors. Thank you much.
--Larry

Ben Wolfson

unread,
Oct 9, 2012, 6:57:13 PM10/9/12
to clo...@googlegroups.com, Larry Travis
On Tue, Oct 9, 2012 at 10:33 AM, Tassilo Horn <ts...@gnu.org> wrote:
> Larry Travis <tra...@cs.wisc.edu> writes:
>
>> and the definition of _list*_ seems to me to be complete without it.
>
> No. It makes
>
> (list* 1 2 3 4 5 [6 7] [8 9]) => (1 2 3 4 5 [6 7] 8 9)
>
> else it would result in (1 2 3 4 5 [6 7] [8 9]), i.e., it implements the
> special meaning of list*'s last parameter.

I took Larry's comment to mean that one could implement list* without
spread, which is true; you just need to change (spread more) to (apply
list* more). In fact apply is defined just below list* and makes use
of both spread and list*, but that's ok:

user> (declare list+)
#'user/list+
user> (defn apply+
([^clojure.lang.IFn f args]
(. f (applyTo (seq args))))
([^clojure.lang.IFn f x args]
(. f (applyTo (list+ x args))))
([^clojure.lang.IFn f x y args]
(. f (applyTo (list+ x y args))))
([^clojure.lang.IFn f x y z args]
(. f (applyTo (list+ x y z args))))
([^clojure.lang.IFn f a b c d & args]
(. f (applyTo (cons a (cons b (cons c (cons d (apply+ list+ args)))))))))
#'user/apply+
user> (defn list+
([args] (seq args))
([a args] (cons a args))
([a b args] (cons a (cons b args)))
([a b c args] (cons a (cons b (cons c args))))
([a b c d & more]
(cons a (cons b (cons c (cons d (apply+ list+ more)))))))
#'user/list+
user> (list+ 1 2 3 4 5 [6 7] [8 9])
(1 2 3 4 5 [6 7] 8 9)

list+ is slower than list*, though.

--
Ben Wolfson
"Human kind has used its intelligence to vary the flavour of drinks,
which may be sweet, aromatic, fermented or spirit-based. ... Family
and social life also offer numerous other occasions to consume drinks
for pleasure." [Larousse, "Drink" entry]

Larry Travis

unread,
Oct 10, 2012, 12:35:23 AM10/10/12
to clo...@googlegroups.com, Ben Wolfson, Larry Travis
Pursuing the thinking behind my original comment, I challenged myself to see how many ways I could write a function equivalent to list* without using spread (assuming I had the full Clojure API available to me -- which, of course, the author of list* did not have).  Here are four ways:

(defn second-list*
  [& args]
  (concat (butlast args) (last args)))

(defn third-list*
  [& args]
  (let [r-args (reverse args)]
    (reduce conj (seq (first r-args)) (rest r-args))))

(defn fourth-list*
  [& args]
  (let [r-args (reverse args)]
    (loop [remaining (rest r-args)
              accum (first r-args)]
      (if (empty? remaining)
        accum
        (recur (rest remaining)
                  (cons (first remaining) accum))))))

(defn fifth-list*
  [& args]
  (if (empty? (rest args))
    (first args)
    (cons (first args) (apply fifth-list* (rest args)))))



But, alas, each of the four is a slowpoke relative to the original:

user> (time (dotimes [_ 100000] (list* 1 2 3 4 5 6 7 [8 9])))
"Elapsed time: 26.558 msecs"

user> (time (dotimes [_ 100000] (second-list* 1 2 3 4 5 6 7 [8 9])))
"Elapsed time: 97.019 msecs"

user> (time (dotimes [_ 100000] (third-list* 1 2 3 4 5 6 7 [8 9])))
"Elapsed time: 96.829 msecs"

user> (time (dotimes [_ 100000] (fourth-list* 1 2 3 4 5 6 7 [8 9])))
"Elapsed time: 100.863 msecs"

user> (time (dotimes [_ 100000] (fifth-list* 1 2 3 4 5 6 7 [8 9])))
"Elapsed time: 75.925 msecs"

Here is the comparable timing for Ben's list+ alternative:

user> (time (dotimes [_ 100000] (list+ 1 2 3 4 5 6 7 [8 9])))
"Elapsed time: 84.891 msecs"

  --Larry


On 10/9/12 5:57 PM, Ben Wolfson wrote:
I took Larry's comment to mean that one could implement list* without
spread ....


Reply all
Reply to author
Forward
0 new messages