{:check ["true"], :rank ["variadic_functions" "apply" "partial" "compose"]}
In this section, we will explore new patterns of programming when functions are treated as data.
Variadic function is a function that can take different number of arguments.
Clojure provides two different ways of implementing variadic functions.
;
; Fixed number of parameters
;
(defn add [x y]
(+ x y))
(add 1 2)
;
; Multiple fixed parameters
;
(defn add
([x y] (+ x y))
([x y z] (+ x y z))
([x1 x2 x3 x4] (+ x1 x2 x3 x4)))
(println (add 1 2))
(println (add 1 2 3))
(println (add 1 2 3 4))
;
; But it's never going to be enough
;
(add 1 2 3 4 5)
To support unbounded many arguments, Clojure can map the extra arguments into a vector parameter, with the syntax:
(defn f [x y & args] ...)
(defn add [x & others]
(loop [sum x
ys others]
(if (empty? ys)
sum
(let [sum' (+ sum (first ys))
others' (rest ys)]
(recur sum' others')))))
(add 1 2 3 4 5 6 7 8 9 10)
apply
is a higher order function that performs function application of a function argument to its data argume
Consider a simple function with three parameters.
(defn add [x y z]
(+ x y z))
(add 1 2 3)
What if we have the parameters as data ?
(let [numbers [1 2 3]]
(add numbers))
We can specify the numbers one by one by their indices.
It works, but it reminds us of procedural languages like Java/C.
(let [numbers [1 2 3]]
(add (numbers 0)
(numbers 1)
(numbers 2)))
apply comes to the rescue.
(apply <function> <arguments-as-sequence>)
(let [numbers [1 2 3]]
(apply add numbers))
apply
can also apply a mixture of single argument values and arguments in a sequence.
(let [numbers [2 3]]
(apply add 1 numbers))
(apply add 1 2 [3])
Many Clojure functions are implemented to accept arbitarily many arguments.
(+ ...)
(str ...)
We can use such variadic functions on both individual values and sequences of values.
;
; Adding up 10 random numbers
;
(+ (rand-int 100)
(rand-int 100)
(rand-int 100)
(rand-int 100)
(rand-int 100)
(rand-int 100)
(rand-int 100)
(rand-int 100)
(rand-int 100)
(rand-int 100))
;
; Build a sequence of five random numbers, and apply +
;
(apply + (for [_ (range 10)] (rand-int 100)))
;
; More functional
;
(apply + (take 10 (repeatedly #(rand-int 100))))
;
; Syntactically beautiful functional code
;
(->> (repeatedly #(rand-int 100))
(take 10)
(apply +))
Suppose we have a function f
that has many parameters. Clojure allows us to build
a partially applied function using:
(partial f <arg> <arg> ...)
(defn say-hello-to [title name profession address]
(let [x (format "Hello %s %s," title name)
y (format " I understand that you are a %s." profession)
z (format " Welcome to %s." address)]
(str x y z)))
(say-hello-to "Dr." "Feynman" "Physicist" "Princeton University")
(say-hello-to "Dr." "Feynman" "Physicist" "Japan")
(say-hello-to "Dr." "Feynman" "painter" "Caltech")
So far, we know that we want to keep generating greetings to Dr. Feynman.
This is called partial application of the function say-hello-to
.
(def f (partial say-hello-to "Dr." "Feynman"))
Now, f
only has two parameters: profession and address.
(f "Biologist" "Brazil")
(f "Computer Scientist" "The Connection Machine")
comp
is a higher-order function that connects two or more single-input functions together.
(comp f3 f2 f1)
The functions on the right are applied first.
(defn f1 [x]
(+ x x))
(defn f2 [x]
(* x x))
(defn f3 [x]
(inc x))
(def g (comp f2 f1))
; (3+3)x(3+3)
; = 6 x 6
; = 36
(g 3)
; (3 x 3) + (3 x 3)
; = 9 + 9
; = 18
((comp f1 f2) 3)
; h(x) = f3(f2(f1(x)))
; = (x+x)*(x+x) + 1
(def h (comp f3 f2 f1))
(h 3)