{:check ["true"],
 :rank ["variadic_functions" "apply" "partial" "compose"]}

Index

Some Higher Order Functions

In this section, we will explore new patterns of programming when functions are treated as data.

  • apply
  • partial
  • compose
  • map
  • filter
  • reduce

Variadic Functions

Variadic functions

Variadic function is a function that can take different number of arguments.

Clojure provides two different ways of implementing variadic functions.

Variadic functions with multiple fixed parameters

In [2]:
;
; Fixed number of parameters
;
(defn add [x y]
    (+ x y))
Out[2]:
#'user/add
In [3]:
(add 1 2)
Out[3]:
3
In [4]:
;
; Multiple fixed parameters
;
(defn add
    ([x y] (+ x y))
    ([x y z] (+ x y z))
    ([x1 x2 x3 x4] (+ x1 x2 x3 x4)))
Out[4]:
#'user/add
In [6]:
(println (add 1 2))
(println (add 1 2 3))
(println (add 1 2 3 4))
3
6
10
Out[6]:
nil
In [7]:
;
; But it's never going to be enough
;
(add 1 2 3 4 5)
Execution error (ArityException) at user/eval4112 (REPL:4).
Wrong number of args (5) passed to: user/add
   AFn.java:   429 clojure.lang.AFn/throwArity
   AFn.java:    48 clojure.lang.AFn/invoke
   core.clj:  3214 clojure.core$eval/invokeStatic
   core.clj:  3210 clojure.core$eval/invoke
   main.clj:   437 clojure.main$repl$read_eval_print__9086$fn__9089/invoke
   main.clj:   458 clojure.main$repl$fn__9095/invoke
   main.clj:   368 clojure.main$repl/doInvoke
RestFn.java:  1523 clojure.lang.RestFn/invoke
   AFn.java:    22 clojure.lang.AFn/run
   AFn.java:    22 clojure.lang.AFn/run
Thread.java:   748 java.lang.Thread/run

Variadic function accepting unbounded arguments

To support unbounded many arguments, Clojure can map the extra arguments into a vector parameter, with the syntax:

(defn f [x y & args] ...)
In [8]:
(defn add [x & others]
    (loop [sum x
           ys others]
        (if (empty? ys)
            sum
            (let [sum' (+ sum (first ys))
                  others' (rest ys)]
                (recur sum' others')))))
Out[8]:
#'user/add
In [9]:
(add 1 2 3 4 5 6 7 8 9 10)
Out[9]:
55

Apply

Apply function

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.

In [2]:
(defn add [x y z]
    (+ x y z))
Out[2]:
#'user/add
In [3]:
(add 1 2 3)
Out[3]:
6

What if we have the parameters as data ?

In [4]:
(let [numbers [1 2 3]]
    (add numbers))
Execution error (ArityException) at user/eval4100 (REPL:2).
Wrong number of args (1) passed to: user/add
   AFn.java:   429 clojure.lang.AFn/throwArity
   AFn.java:    32 clojure.lang.AFn/invoke
   core.clj:  3214 clojure.core$eval/invokeStatic
   core.clj:  3210 clojure.core$eval/invoke
   main.clj:   437 clojure.main$repl$read_eval_print__9086$fn__9089/invoke
   main.clj:   458 clojure.main$repl$fn__9095/invoke
   main.clj:   368 clojure.main$repl/doInvoke
RestFn.java:  1523 clojure.lang.RestFn/invoke
   AFn.java:    22 clojure.lang.AFn/run
   AFn.java:    22 clojure.lang.AFn/run
Thread.java:   748 java.lang.Thread/run

We can specify the numbers one by one by their indices.

  • Cumbersome
  • Slow in performance

It works, but it reminds us of procedural languages like Java/C.

In [5]:
(let [numbers [1 2 3]]
    (add (numbers 0)
         (numbers 1)
         (numbers 2)))
Out[5]:
6

apply comes to the rescue.

(apply <function> <arguments-as-sequence>)

In [6]:
(let [numbers [1 2 3]]
    (apply add numbers))
Out[6]:
6

Mixing argument values and an argument sequence

apply can also apply a mixture of single argument values and arguments in a sequence.

  • single argument values are prepended to the sequence.
  • then the whole sequence is applied to the function.
In [7]:
(let [numbers [2 3]]
    (apply add 1 numbers))
Out[7]:
6
In [9]:
(apply add 1 2 [3])
Out[9]:
6

Apply arguments to variadic functions

Many Clojure functions are implemented to accept arbitarily many arguments.

  • Most arithmetic operators: (+ ...)
  • Stringify one or more data values: (str ...)

We can use such variadic functions on both individual values and sequences of values.

In [16]:
;
; 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))
Out[16]:
426
In [17]:
;
; Build a sequence of five random numbers, and apply +
;
(apply + (for [_ (range 10)] (rand-int 100)))
Out[17]:
457
In [18]:
;
; More functional
;
(apply + (take 10 (repeatedly #(rand-int 100))))
Out[18]:
444
In [20]:
;
; Syntactically beautiful functional code
;
(->> (repeatedly #(rand-int 100))
     (take 10)
     (apply +))
Out[20]:
440

Partial

Partial applications

Suppose we have a function f that has many parameters. Clojure allows us to build a partially applied function using:

(partial f <arg> <arg> ...)
In [3]:
(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)))
Out[3]:
#'user/say-hello-to
In [6]:
(say-hello-to "Dr." "Feynman" "Physicist" "Princeton University")
Out[6]:
"Hello Dr. Feynman, I understand that you are a Physicist. Welcome to Princeton University."
In [7]:
(say-hello-to "Dr." "Feynman" "Physicist" "Japan")
Out[7]:
"Hello Dr. Feynman, I understand that you are a Physicist. Welcome to Japan."
In [8]:
(say-hello-to "Dr." "Feynman" "painter" "Caltech")
Out[8]:
"Hello Dr. Feynman, I understand that you are a painter. Welcome to Caltech."

So far, we know that we want to keep generating greetings to Dr. Feynman.

  1. We can be sure of the first two arguments.
  2. We can build a function specific to these two arguments.

This is called partial application of the function say-hello-to.

In [9]:
(def f (partial say-hello-to "Dr." "Feynman"))
Out[9]:
#'user/f

Now, f only has two parameters: profession and address.

In [10]:
(f "Biologist" "Brazil")
Out[10]:
"Hello Dr. Feynman, I understand that you are a Biologist. Welcome to Brazil."
In [12]:
(f "Computer Scientist" "The Connection Machine")
Out[12]:
"Hello Dr. Feynman, I understand that you are a Computer Scientist. Welcome to The Connection Machine."

Compose

Compose functions

  • Functions transform input arguments into an output value.
  • When we limit ourselves to single input functions, we can chain them up into a pipeline.

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.

In [1]:
(defn f1 [x]
    (+ x x))
Out[1]:
#'user/f1
In [2]:
(defn f2 [x]
    (* x x))
Out[2]:
#'user/f2
In [3]:
(defn f3 [x]
    (inc x))
Out[3]:
#'user/f3
  • Consider: $g(x) = (x + x) \times (x + x)$.
  • This can be written as: $g(x) = f_2(f_1(x))$
  • Or, in terms of composition, $g(x) = (f_2 \circ f_1)(x)$
In [4]:
(def g (comp f2 f1))
Out[4]:
#'user/g
In [6]:
;   (3+3)x(3+3)
; = 6 x 6
; = 36
(g 3)
Out[6]:
36
In [7]:
;   (3 x 3) + (3 x 3)
; = 9 + 9
; = 18
((comp f1 f2) 3)
Out[7]:
18
In [9]:
; h(x) = f3(f2(f1(x)))
;      = (x+x)*(x+x) + 1
(def h (comp f3 f2 f1))
Out[9]:
#'user/h
In [10]:
(h 3)
Out[10]:
37