Learning Clojure Part 4: Functions
Tags: clojure, tutorial
Published: 2021-01-08 13:01:54

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:
```clojure
((or + -) 1 2 3 4 5)
;=> 15
```

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.
```clojure
(inc 41)
;=> 42

(map inc [1 2 3 4 5])
;=> (2 3 4 5 6)
```

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.
```clojure
(+ (inc 1) (* 2 (+ 1 1)))
(+ 2 (* 2 (+ 1 1)))
(+ 2 (* 2 2))
(+ 2 4)
6
```

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`.
```clojure
(if transaction-approved
 (send-money 5000)
 (send-money 0)
)
```
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:
```clojure
(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"
```
There can be zero or more parameters.
```clojure
[] ;zero
[input] ; one
[one two] ; two
```

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. 
```clojure
(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"
```

We can also use variable length arguments, using the `&` character. 
```clojure
(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")
```

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. 
```clojure
(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
```

You can do the same with maps.
```clojure
(defn say-hello
 [{first :first last :last}]
 (str "Hello my friend " first " " last)
)

(say-hello {:first "Darth" :last "Vader"})
;=> "Hello my friend Darth Vader"
```

But if we just want to break keywords of the map we can use `:keys` keyword. 
```clojure
(defn say-hello
 [{:keys [first last]}]
 (str "Hello my friend " first " " last)
)

(say-hello {:first "Darth" :last "Vader"})
;=> "Hello my friend Darth Vader"
```

We can even retain access to the original with `:as`.
```clojure
(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"}
```

The function body can contain multiple forms. The form is returned. 
```clojure
(defn return-last
 []
 1
 (+ 1 2)
 "This is the end."
)

(return-last)
;=> "This is the end."
```

## 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).
```clojure
(fn [param] body)
```

More concrete example:
```clojure
(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)
```

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`.
```clojure
(def adder (fn [number] (+ 42 number)))
(adder 1)
;=> 43
```

And if we find the `fn` form too long we can use the shortcut form.
```clojure
#(+ % 42)

(#(+ % 42) 1)
;=> 43
```

This may look strange, but the `%` represents the argument. Lets to easier to understand example:
```clojure
(map #(str "Hello dear " %) ["Darth Vader" "Anakin" "Yoda" "Obi-wan"])
;=> ("Hello dear Darth Vader" "Hello dear Anakin" "Hello dear Yoda" "Hello dear Obi-wan")
```

If our function needs to take more than one argument we can use `%1`, `%2`, etc..
```clojure
(#(str %1 " and " %2) "dogs" "cats")
;=> "dogs and cats"
```

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. 
```clojure
(defn plus-maker
 "Create a custom function that adds to a number"
 [add]
 #(+ % add)
)
(def plus42 (plus-maker 42))
(plus42 1)
;=> 43
```

---

You can follow me on [Twitter](https://twitter.com/pavel_polivka) to get more content like this.