问题
In answering a question about a function that maps over multiple functions with the same arguments (A: juxt), I came up with a function that basically took the same form as juxt, but used map:
(defn could-be-lazy-juxt
[& funs]
(fn [& args]
(map apply funs (repeat args))))
=> ((juxt inc dec str) 1)
[2 0 "1"]
=> ((could-be-lazy-juxt inc dec str) 1)
(2 0 "1")
=> ((juxt * / -) 6 2)
[12 3 4]
=> ((could-be-lazy-juxt * / -) 6 2)
(12 3 4)
I have little clue about the laziness or performance of it, but timing in the REPL does suggest something lazy-ish is going on.
=> (time (apply (juxt + -) (range 1 100)))
"Elapsed time: 0.097198 msecs"
[4950 -4948]
=> (time (apply (could-be-lazy-juxt + -) (range 1 100)))
"Elapsed time: 0.074558 msecs"
(4950 -4948)
=> (time (apply (juxt + -) (range 10000000)))
"Elapsed time: 1019.317913 msecs"
[49999995000000 -49999995000000]
=> (time (apply (could-be-lazy-juxt + -) (range 10000000)))
"Elapsed time: 0.070332 msecs"
(49999995000000 -49999995000000)
I'm sure this function is not really that quick (the print of the outcome 'feels' about as long in both). Doing a 'take x' on the function only limits the amount of functions evaluated, which probably is limited in it's applicability, and limiting the other parameters by 'take' should be just as lazy in normal juxt.
Is this juxt really lazy ? Would a lazy juxt bring anything useful to the table, for instance as a compositing step between other lazy functions ? What are the performance (mem / cpu / object count / compilation) implications ? Why is the Clojure juxt implemented with a reduce and returns a vector, which breaks laziness ?
回答1:
Yes your implementation of juxt is lazy by virtue of only calling map which is lazy.
it's hard to call it in it's present form with out realizing the arguments in the caller (by using apply. so I changed it a bit to take a sequence of functions:
user> (defn could-be-lazy-juxt
[funs]
(fn [& args]
(map #(apply %1 %2) funs (repeat args))))
#'user/could-be-lazy-juxt
then define a lazy sequence of functions that make a lot of output when it's realized
user> (defn loud-seq [len] (take len (map #(do (println "produced a function") %) (cycle [inc dec]))))
#'user/loud-seq
then use juxt to make a function out of this lazy sequence of functions
user> (def f (could-be-lazy-juxt (loud-seq 50)))
#'user/f
as you can see the list is still lazy, it realizes it's list of functions when it's resulting function is called.
so lets call it:
user> (f 1)
(produced a function
produced a function
2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 produced a function
produced a function
0 2 0)
user>
I leave the reasons for doing so up to you ;)
来源:https://stackoverflow.com/questions/10049925/any-merit-to-a-lazy-ish-juxt-function