Skip to content
AgileLogics
7 min read

Functions

Prerequisites: Conditionals

defn defines a named function:

(defn greet
  "Returns a greeting for name."
  [name]
  (str "Hello, " name "!"))

(defn add [a b]
  (+ a b))

(println (greet "Ada"))
(println (add 2 3))
Hello, Ada!
5
  • The optional string after the name is a docstring, shown by (doc greet).
  • Parameters go in a vector [name].
  • The value of the last expression is returned — there's no return keyword.

Anonymous functions

(def square (fn [x] (* x x)))
(println (square 5))

(println (map (fn [x] (* x 10)) [1 2 3]))
(println (map #(* % 10) [1 2 3]))          ; short form
(println (map #(+ %1 %2) [1 2 3] [10 20 30]))
25
(10 20 30)
(10 20 30)
(11 22 33)
  • (fn [params] body) creates a function without a name.
  • #( ... ) is shorthand: % is the single argument, %1, %2 are numbered ones. Keep #() for tiny functions; don't nest them.
  • Functions are values: you can store them, pass them, and return them.

Multiple arities

(defn greet
  ([] (greet "world"))
  ([name] (greet name "Hello"))
  ([name greeting] (str greeting ", " name "!")))

(println (greet))
(println (greet "Ada"))
(println (greet "Ada" "Welcome"))
Hello, world!
Hello, Ada!
Welcome, Ada!

One function can have several bodies for different numbers of arguments — Clojure's way of providing default values.

Variadic functions

(defn total [& numbers]
  (apply + numbers))

(defn log [level & parts]
  (println (str "[" level "]") (apply str parts)))

(println (total 1 2 3 4))
(println (total))
(log "INFO" "Server started on port " 8080)
10
0
[INFO] Server started on port 8080

& numbers collects the remaining arguments into a sequence (or nil if there are none). apply calls a function with the items of a collection as its arguments.

Pre- and postconditions

(defn percentage [part whole]
  {:pre [(number? part) (pos? whole)]}
  (* 100.0 (/ part whole)))

(println (percentage 1 4))
(println (try (percentage 1 0)
              (catch AssertionError e (.getMessage e))))
25.0
Assert failed: (pos? whole)

A map of :pre (and :post) checks right after the parameters guards a function's inputs (and outputs).

Common mistakes

  • Calling a function with the wrong number of arguments throws an ArityException — the message tells you the function and the count.
  • Wrapping a value in extra parentheses: ((+ 1 2)) tries to call the number 3.

Practice

Write palindrome? that ignores case, and test it with "Level" and "Clojure".

Show solution
(require '[clojure.string :as str])

(defn palindrome? [s]
  (let [lower (str/lower-case s)]
    (= lower (str/reverse lower))))

(println (palindrome? "Level"))
(println (palindrome? "Clojure"))
true
false