tags:

views:

163

answers:

3

I know that the -> form can be used to pass the results of one function result to another:

(f1 (f2 (f3 x))) 
(-> x f3 f2 f1) ; equivalent to the line above

(taken from the excellent Clojure tutorial at ociweb)

However this form requires that you know the functions you want to use at design time. I'd like to do the same thing, but at run time with a list of arbitrary functions.

I've written this looping function that does it, but I have a feeling there's a better way:

(defn pipe [initialData, functions]
  (loop [
      frontFunc (first functions)
      restFuncs (rest functions)
      data initialData ]
    (if frontFunc
      (recur (first restFuncs) (rest restFuncs) (frontFunc data) )
      data )
  ) )

What's the best way to go about this?

+14  A: 

I must admit I'm really new to clojure and I might be missing the point here completely, but can't this just be done with a comp?

user> (defn fn1 [x] (+ 2 x))
user> (defn fn2 [x] (/ x 3))
user> (defn fn3 [x] (* 1.2 x))
user> (defn pipe [initial-data & my-functions] ((apply comp my-functions) initial-data))
user> (pipe 2 fn1 fn2 fn3)
2.8
jandot
Or without defining those functions beforehand: (pipe 2 #(+ 2 %) #(/ % 3) #(* 1.2 %))
jandot
Yep you're right, I'm new to Clojure too and hadn't come across comp yet. But what if I have a list of functions, rather than collected arguments? How do I break up the list so comp will accept it?
tenpn
...cuz without that detail, I can't use arbitrary run-time functions, and it's really just the same problem restated.
tenpn
Not 100% sure what you mean with "a list of functions, rather than collected arguments"? But if I guess right: have a look at (apply). That's what apply does: it takes a list and converts that list to arguments to a function. For example: (comp [fn1 fn2 fn3]) does not work, but (apply comp [fn1 fn2 fn3]) does.
jandot
It should be noted that if non-commutativity is important, `(apply comp [fn1 fn2 fn3])` applies the pipe right-to-left. Use `(apply comp (reverse [fn1 fn2 fn3]))` to apply left-to-right.
tenpn
+2  A: 

If functions is a sequence of functions, you can reduce it using comp to get a composed function. At a REPL:

user> (def functions (list #(* % 5) #(+ % 1) #(/ % 3)))
#'user/my-list
user> ((reduce comp functions) 9)
20

apply also works in this case because comp takes a variable number of arguments:

user> (def functions (list #(* % 5) #(+ % 1) #(/ % 3)))
#'user/my-list
user> ((apply comp functions) 9)
20
Brian Cooley
+2  A: 

You can do this with a plain old reduce:

(defn pipe [x fs] (reduce (fn [acc f] (f acc)) x fs))

That can be shortened to:

(defn pipe [x fs] (reduce #(%2 %1) x fs))

Used like this:

user> (pipe [1 2 3] [#(conj % 77) rest reverse (partial map inc) vec])
[78 4 3]
Brian Carper