Learning Clojure Part 4: Functions

Pavel Polívka - Jan 8 '21 - - Dev Community

In the previous parts, we used a lot of functions already so we pretty much have an idea on how to call a function.

Let go over it once again. all the expressions have the same syntax. (, operator, operands, ).
An operator is a function or an expression where the result is a function.
An operand can be any value, function, or expression.
All the non-nil and not false values are considered true.

This lets us do a interesting things:

((or + -) 1 2 3 4 5)
;=> 15
Enter fullscreen mode Exit fullscreen mode

Let's have a more complex example. Take functions map and inc. inc increments number by 1. map takes a function and collection and creates a new list, where the function is applied to the collection.

(inc 41)
;=> 42

(map inc [1 2 3 4 5])
;=> (2 3 4 5 6)
Enter fullscreen mode Exit fullscreen mode

Another thing we need to know is that Clojure evaluates all function arguments recursively, before passing them into function.

Here is a step by step example how Clojure would evaluete.

(+ (inc 1) (* 2 (+ 1 1)))
(+ 2 (* 2 (+ 1 1)))
(+ 2 (* 2 2))
(+ 2 4)
6
Enter fullscreen mode Exit fullscreen mode

The exception to this rule is something called a "special form". Unlike function, special forms do not evaluate all the arguments. The best example of special for is if.

(if transaction-approved
 (send-money 5000)
 (send-money 0)
)
Enter fullscreen mode Exit fullscreen mode

The above example makes it clear why. We want to send money only when the transaction was approved.

Special forms cannot be used as arguments of functions.

Defining functions

We already did define one function in part two using the defn expression. Now we will go over the whole process again and we will go deep.

Function definitions have five parts:

  • defn
  • Name of the function
  • Optional string describing the function
  • Parameters in brackets
  • Body of the function

An example of a function definition would be:

(defn make-it-awesome
 "Returns back a string, that will be awesome"
 [input]
 (str "OMG this is so awesome " input " AWESOME AWESOME AWESOME")
)

(make-it-awesome "lightsaber")
;=> "OMG this is so awesome lightsaber AWESOME AWESOME AWESOME"
Enter fullscreen mode Exit fullscreen mode

There can be zero or more parameters.

[] ;zero
[input] ; one
[one two] ; two
Enter fullscreen mode Exit fullscreen mode

Functions support parameter overloading. So you can define the same function name multiple times, with different parameters. This the main way of providing default values.

(defn shoot
 ([shooter target]
 (str shooter " shoots at " target)
 )
 ([target]
 (shoot "Agent Smith" target)
 )
)

(shoot "Neo" "Agent Smith")
;=> "Neo shoots at Agent Smith"

(shoot "Neo")
;=> "Agent Smith shoots at Neo"
Enter fullscreen mode Exit fullscreen mode

We can also use variable length arguments, using the & character.

(defn killing-spree
 [& targets]
 (map shoot targets)
)

(killing-spree "Morpheus" "Trinity" "Neo")
;=> ("Agent Smith shoots at Morpheus" "Agent Smith shoots at Trinity" "Agent Smith shoots at Neo")
Enter fullscreen mode Exit fullscreen mode

Another quite awesome thing we can do is pass the collection of arguments, and let Clojure destruct that collection and name some members of the collection with meaningful names.

(defn pack
 [[most-important super-needed & others]]
 (println "Packing for a trip.")
 (println (str "Most important thing is " most-important))
 (println (str "You cannot go without " super-needed))
 (println (str "Also: " (clojure.string/join ", " others)))
)

(pack ["wallet" "phone" "boxers" "deodorant" "socks"])
;=>Packing for a trip.
;Most important thing is wallet
;You cannot go without phone
;Also: boxers, deodorant, socks
Enter fullscreen mode Exit fullscreen mode

You can do the same with maps.

(defn say-hello
 [{first :first last :last}]
 (str "Hello my friend " first " " last)
)

(say-hello {:first "Darth" :last "Vader"})
;=> "Hello my friend Darth Vader"
Enter fullscreen mode Exit fullscreen mode

But if we just want to break keywords of the map we can use :keys keyword.

(defn say-hello
 [{:keys [first last]}]
 (str "Hello my friend " first " " last)
)

(say-hello {:first "Darth" :last "Vader"})
;=> "Hello my friend Darth Vader"
Enter fullscreen mode Exit fullscreen mode

We can even retain access to the original with :as.

(defn say-hello
 [{:keys [first last] :as person}]
 (println (str "Hello my friend " first " " last))
 (println (str "Original map was " person))
)
(say-hello {:first "Darth" :last "Vader"})
;Hello my friend Darth Vader
;Original map was {:first "Darth", :last "Vader"}
Enter fullscreen mode Exit fullscreen mode

The function body can contain multiple forms. The form is returned.

(defn return-last
 []
 1
 (+ 1 2)
 "This is the end."
)

(return-last)
;=> "This is the end."
Enter fullscreen mode Exit fullscreen mode

Anonymous Functions

Clojure also supports anonymous functions, functions without names. We can use the fn form. Works exactly like the defn form (excluding the names).

(fn [param] body)
Enter fullscreen mode Exit fullscreen mode

More concrete example:

(map (fn [number] (+ 42 number)) [0 1 2 3 4 5 6 7 8 9 10])
;=> (42 43 44 45 46 47 48 49 50 51 52)
Enter fullscreen mode Exit fullscreen mode

We can use the same as with the normal function. Parameter lists, destructing, etc... We can even assign a name to this function using def.

(def adder (fn [number] (+ 42 number)))
(adder 1)
;=> 43
Enter fullscreen mode Exit fullscreen mode

And if we find the fn form too long we can use the shortcut form.

#(+ % 42)

(#(+ % 42) 1)
;=> 43
Enter fullscreen mode Exit fullscreen mode

This may look strange, but the % represents the argument. Lets to easier to understand example:

(map #(str "Hello dear " %) ["Darth Vader" "Anakin" "Yoda" "Obi-wan"])
;=> ("Hello dear Darth Vader" "Hello dear Anakin" "Hello dear Yoda" "Hello dear Obi-wan")
Enter fullscreen mode Exit fullscreen mode

If our function needs to take more than one argument we can use %1, %2, etc..

(#(str %1 " and " %2) "dogs" "cats")
;=> "dogs and cats"
Enter fullscreen mode Exit fullscreen mode

The remaining arguments can be captured by %&.

Functions returning functions

We have already seen that functions return functions. The returned functions are called closures. That means that they can access all the variables that were in the scope when the function was created.

(defn plus-maker
 "Create a custom function that adds to a number"
 [add]
 #(+ % add)
)
(def plus42 (plus-maker 42))
(plus42 1)
;=> 43
Enter fullscreen mode Exit fullscreen mode

You can follow me on Twitter to get more content like this.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .