Language: Functions
This guide covers:
- How to define functions
- How to invoke functions
- Multi-arity functions
- Variadic functions
- Higher order functions
- Other topics related to functions
This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images & stylesheets). The source is available on Github.
What Version of Clojure Does This Guide Cover?
This guide covers Clojure 1.11.
Overview
Clojure is a functional programming language. Naturally, functions are very important part of Clojure.
How To Define Functions
Functions are typically defined using the defn macro:
(defn round
[d precision]
(let [factor (Math/pow 10 precision)]
(/ (Math/floor (* d factor)) factor)))
Functions can have doc strings (documentation strings) and it is a good idea to document functions that are part of the public API:
(defn round
"Round down a double to the given precision (number of significant digits)"
[d precision]
(let [factor (Math/pow 10 precision)]
(/ (Math/floor (* d factor)) factor)))
The benefit of writing docstrings is that they show up in editors and the REPL:
user=> (doc round)
user/round
([d precision])
Round down a double to the given precision (number of significant digits)
nil
In Clojure, function arguments may have optional type hints:
(defn round
[^double d ^long precision]
(let [factor (Math/pow 10 precision)]
(/ (Math/floor (* d factor)) factor)))
The result of a function can also have a type hint (note that it goes in front of the argument list, not in front of the function name):
(defn round
^double [^double d ^long precision]
(let [factor (Math/pow 10 precision)]
(/ (Math/floor (* d factor)) factor)))
Type hints sometimes allow the compiler to avoid reflective method calls when using Java interop (as the above examples do) and may produce significantly more efficient bytecode. However, as a rule of thumb, it is usually not necessary to use type hints. Start writing your code without them. The compiler is also free to ignore provided hints.
The examples above use Java interop (static methods of the java.lang.Math
class).
As of Clojure 1.11, clojure.math
is available and provides a more idiomatic
way to perform mathematical operations that do not need type hints:
(require '[clojure.math :as math])
(defn round
[d precision]
(let [factor (math/pow 10 precision)]
(/ (math/floor (* d factor)) factor)))
Functions can also define preconditions and postconditions that put restrictions on argument values and the value function returns:
(require '[clojure.math :as math])
(defn round
"Round down a double to the given precision (number of significant digits)"
[d precision]
{:pre [(not-nil? d) (not-nil? precision)]}
(let [factor (math/pow 10 precision)]
(/ (math/floor (* d factor)) factor)))
In the example above, we use preconditions to check that both arguments are not
nil. The not-nil?
macro (or function) is not
demonstrated in this example and assumed to be implemented elsewhere.
Anonymous Functions
Anonymous functions are defined using the fn
special form:
(fn [x]
(* 2 x))
Anonymous functions can be bound to locals, passed between functions (higher order functions are covered later in this document) and returned from functions:
(let [f (fn [x]
(* 2 x))]
(map f (range 0 10)))
There is also a reader macro for anonymous functions:
(let [f #(* 2 %)]
(map f (range 0 10)))
The %
in the example above means "the first argument". To refer to more than one argument, use %1
, %2
and so on:
;; an anonymous function that takes 3 arguments and adds them together
(let [f #(+ %1 %2 %3)]
(f 1 2 3))
Please use this reader macro sparingly; excessive use may lead to unreadable code.
How To Invoke Functions
Functions are invoked by placing a function in the leading position (the calling position) of a list:
(format "Hello, %s" "world")
This works also if you have a function stored in a local, a var or passed as an argument:
(let [f format]
(f "Hello, %s" "world"))
Alternatively, you can call a function using clojure.core/apply
(apply format "Hello, %s" ["world"])
(apply format "Hello, %s %s" ["Clojure" "world"])
clojure.core/apply
is usually only necessary when calling variadic functions or having the list of arguments passed in
as a collection.
Multi-arity Functions
Functions in Clojure can have multiple arities, or sets of arguments:
(require '[clojure.math :as math])
(defn tax-amount
([amount]
(tax-amount amount 35))
([amount rate]
(math/round (double (* amount (/ rate 100))))))
In the example above, the version of the function that takes only one argument (so called one-arity or 1-arity function) calls another version (2-arity) with a default parameter. This is a common use case for multiple arities: to have default argument values. Clojure is a hosted language and JVM (and JavaScript VMs, for that matter) does not support default argument values, however, it does support method overloading and Clojure takes advantage of this.
Arities in Clojure can only differ by the number of arguments, not types. This is because Clojure is a strongly dynamically typed language and type information about parameters may or may not be available to the compiler.
A larger example:
(defn my-range
([]
(my-range 0 Double/POSITIVE_INFINITY 1))
([end]
(my-range 0 end 1))
([start end]
(my-range start end 1))
([start end step]
(comment Omitted for clarity)))
Destructuring of Function Arguments
Sometimes function arguments are data structures: vectors, sequences, maps. To access parts of such data structure, you may do something like this:
(defn currency-of
[m]
(let [currency (get m :currency)]
currency))
For vector arguments:
(defn currency-of
[pair]
(let [amount (first pair)
currency (second pair)]
currency))
However, this is boilerplate code that has little to do with what the function really does. Clojure lets developer destructure parts of arguments, for both maps and sequences.
Positional Destructuring
Destructuring over vectors (positional destructuring) works like this: you replace the argument with a vector that has "placeholders" (symbols) in positions you want to bind. For example, if the argument is known to be a pair and you need second argument, it would look like this:
(defn currency-of
[[amount currency]]
currency)
In the example above the first element in the pair is bound to amount
and the second one is bound to
currency
. So far so good. However, notice that we do not use the amount
local. In that case, we can
ignore it by replacing it with an underscore:
(defn currency-of
[[_ currency]]
currency)
Destructuring can nest (destructure deeper than one level):
(defn first-first
[[[i _] _]]
i)
While this article does not cover let
and locals, it is worth demonstrating that positional destructuring works
exactly the same way for let bindings:
(let [pair [10 :gbp]
[_ currency] pair]
currency)
Map Destructuring
Destructuring over maps and records (map destructuring) works slightly differently:
(defn currency-of
[{currency :currency}]
currency)
In this case example, we want to bind the value for key :currency
to currency
. Keys don't have to be
keywords:
(defn currency-of
[{currency "currency"}]
currency)
(defn currency-of
[{currency 'currency}]
currency)
When destructuring multiple keys at once, it is more convenient to use a slightly different syntax:
(defn currency-of
[{:keys [currency amount]}]
currency)
The example above assumes that map keys will be keywords and we are interested in two values: currency
and :amount
. The same can be done for strings:
(defn currency-of
[{:strs [currency amount]}]
currency)
and symbols:
(defn currency-of
[{:syms [currency amount]}]
currency)
In practice, keywords are very commonly used for map keys so destructuring with {:keys [...]}
is very common
as well.
If you want to destructure a map that has namespaced keys, you can either
specify the prefix on each name in the binding or as a prefix on :keys
itself:
;; instead of {:currency "GBP" :amount 95.99} let's assume we have namespaced
;; keys: {:invoice/currency "GBP" :invoice/amount 95.99}
(defn currency-of
[{:keys [invoice/currency invoice/amount]}] ; prefixed names
currency) ; bind to unprefixed symbols
;; or
(defn currency-of
[{:invoice/keys [currency amount]}] ; prefixed :keys
currency) ; bind to unprefixed symbols
Map destructuring also lets us specify default values for keys that may be missing:
(defn currency-of
[{:keys [currency amount] :or {currency :gbp}}]
currency)
This is very commonly used for implementing functions that take "extra options" (faking named arguments support).
Just like with positional destructuring, map destructuring works exactly the same way for let bindings:
(let [money {:currency :gbp :amount 10}
{currency :currency} money]
currency)
Variadic Functions
Variadic functions are functions that take varying number of arguments (some arguments are optional). Two examples
of such function in clojure.core
are clojure.core/str
and clojure.core/format
:
(str "a" "b")
; ⇒ "ab"
(str "a" "b" "c")
; ⇒ "abc"
(format "Hello, %s" "world")
; ⇒ "Hello, world"
(format "Hello, %s %s" "Clojure" "world")
; ⇒ "Hello, Clojure world"
To define a variadic function, prefix optional arguments with an ampersand (&
):
(defn log
[message & args]
(comment ...))
In the example above, one argument is required and the rest is optional. Variadic functions are invoked as usual:
(defn log
[message & args]
(println "args: " args))
(log "message from " "192.0.0.76")
(log "message from " "192.0.0.76" "service:xyz")
As you can see, optional arguments (args
) are packed into a list.
Extra Arguments (aka Named Parameters)
Named parameters are achieved through the use of destructuring a variadic function.
Approaching named parameters from the standpoint of destructuring a variadic function allows for more clearly readable function invocations. This is an example of named parameters:
(defn job-info
[& {:keys [name job income] :or {job "unemployed" income "$0.00"}}]
(if name
[name job income]
(println "No name specified")))
Using the function looks like this:
(job-info :name "Robert" :job "Engineer")
;; ["Robert" "Engineer" "$0.00"]
(job-info :job "Engineer")
;; No name specified
Without the use of a variadic argument list, you would have to call the function with a single map argument such as {:name "Robert" :job "Engineer}
.
As of Clojure 1.11, you can also pass named parameters as a map:
(job-info {:name "Robert" :job "Engineer"})
;;=> ["Robert" "Engineer" "$0.00"]
(job-info {}:job "Engineer"})
;;=> No name specified
This allows for easier programmatic invocation of functions with named parameters where you might be building a map of parameters and passing it through your code.
Keyword default values are assigned by use of the :or
keyword followed by a map of keywords to their default value.
Keywords not present and not given a default will be nil.
Higher Order Functions
Higher-order functions (HOFs) are functions that take other functions as arguments. HOFs
are an important functional programming technique and are quite commonly used in Clojure. One example
of an HOF is a function that takes a function and a collection and returns a collection of elements
that satisfy a condition (a predicate). In Clojure, this function is called clojure.core/filter
:
(filter even? (range 0 10)) ; ⇒ (0 2 4 6 8)
In the example above, clojure.core/filter
takes clojure.core/even?
as an argument.
clojure.core
has dozens of other higher-order functions. The most commonly used ones are covered in clojure.core Overview.
Private Functions
Functions in Clojure can be private to their namespace.
They are covered in more detail in the Namespaces guide.
Keywords as Functions
In Clojure, keywords can be used as functions. They take a map or record and look themselves up in it:
(:age {:age 27 :name "Michael"})
; ⇒ 27
This is commonly used with higher order functions:
(map :age [{:age 45 :name "Joe"}
{:age 42 :name "Jill"}
{:age 17 :name "Matt"}])
;; ⇒ (45 42 17)
and the ->
macro:
(-> [{:age 45 :name "Joe"} {:age 42 :name "Jill"}]
first
:name)
;; ⇒ "Joe"
Maps as Functions
Clojure maps are also functions that take keys and look up values for them:
({:age 42 :name "Joe"} :name)
; ⇒ "Joe"
({:age 42 :name "Joe"} :age)
; ⇒ 42
({:age 42 :name "Joe"} :unknown)
; ⇒ nil
Note that this is not true for Clojure records, which are almost identical to maps in other cases.
Sets as Functions
(#{1 2 3} 1)
; ⇒ 1
(#{1 2 3} 10)
; ⇒ nil
(#{:us :au :ru :uk} :uk)
; ⇒ :uk
(#{:us :au :ru :uk} :cn)
; ⇒ nil
This is often used to check if a value is in a set:
(when (countries :in)
(comment ...))
(if (countries :in)
(comment Implement positive case)
(comment Implement negative case))
because everything but false
and nil
evaluates to true
in Clojure.
Clojure Functions As Comparators
Clojure functions implement the java.util.Comparator interface and can be used as comparators.
Wrapping Up
Functions are at the heart of Clojure. They are defined using the defn
macro, can have multiple arities,
be variadic and support parameter destructuring. Function arguments and return value can optionally be
type hinted.
Functions are first class values and can be passed to other functions (called Higher Order Functions or HOFs). This is fundamental to functional programming techniques.
Several core data types behave like functions. When used reasonably, this can lead to more concise, readable code.
Contributors
Michael Klishin michael@defprotocol.org, 2012 (original author)