Silvia Pan Logo
July 10, 2020

How to Use Anonymous Functions in Clojure

What makes up defn

Named functions are defined with defn, which is a combination of def (creating variable) and fn (creating function).

;; Function named 'do-something'
(def do-something
    (fn [arg1 arg2]
        (...)))

;; Equivalent, more concise way to write function
(defn do-something [arg1 arg2]
    (...))

When a function has no name, i.e. no def, it’s an anonymous function.

;; Function named "double"
(def double
    (fn [x]
        (* 2 x)))

;; Anonymous function
(fn [x] (* 2 x))

;; Invoke anonymous function
((fn [x] (* 2 x)) 10) ;; 20

Using anonymous functions with map and filter

When a function is called only once a name isn’t required. You’ll see this pattern when working with higher-order functions, such as map, filter, and reduce, where the anonymous functions aren’t reused elsewhere.

Let’s see how our doubling function would work with map.

;; Structure for map function
(map function collection)

;; Function to apply to each item in collection
(fn [x] (* 2 x))

;; Collection to map through
[1 2 3 4 5]

;; Map through collection and double each element
(map (fn [x] (* 2 x)) [1 2 3 4 5]) ;; (2 4 6 8 10)

Shorthand for writing anonymous functions

When a function has only one argument, I prefer using the shorter form for anonymous functions: #().

Arguments aren’t in a list. Instead, they’re represented as:

  • % for single argument
  • %1, %2, %3, ... for multiple arguments
  • %& for any remaining arguments
;; Single argument
(fn [x] (* 2 x))
#(* 2 %)

;; Multiple arguments
(fn [x y] (+ x y))
#(+ %1 %2)

;; Remaining arguments
(fn [x & args] (reduce + x args))
#(reduce + % %&)

When functions have more than two arguments, I’d recommend using the longer (fn) form because it can be confusing to keep track of what each argument is.

Using anonymous functions with map and filter

In a previous example we’ve used map to double each number in a vector. Let’s work a more sophisticated collection!

;; Directory of all movies available for rent
(def movies
    [{:id 1 :name "Hobbit" :price 2.50 :promoted true  :genre :fantasy}
     {:id 2 :name "Frozen" :price 6.00 :promoted false :genre :comedy}
     {:id 3 :name "Wall-E" :price 3.50 :promoted true  :genre :comedy}
     {:id 4 :name "Avatar" :price 3.50 :promoted false :genre :sci-fi}
     {:id 5 :name "Aliens" :price 1.00 :promoted true  :genre :sci-fi}])

Getting values with map

When we’re getting values, map creates a new sequence and movies is not changed in any way.

;; Get only the ID and name of each movie.
(map (fn [movie] (select-keys movie [:id :name])) movies)
(map #(select-keys % [:id :name]) movies)

;; ({:id 1, :name "Hobbit"}
;;  {:id 2, :name "Frozen"}
;;  {:id 3, :name "Wall-E"}
;;  {:id 4, :name "Avatar"}
;;  {:id 5, :name "Aliens"})

Updating values with map

It’s misleading to say we’re updating values because movies never gets changed. Instead we’re creating a new sequence with the updated values.

;; There's a 50% discount for renting movies. Return movies with updated prices.
(map (fn [movie] (merge movie {:price (* 0.5 (:price movie))})) movies)
(map #(merge % {:price (* 0.5 (:price %))}) movies)

;; ({:id 1, :name "Hobbit", :price 1.25, :promoted true,  :genre :fantasy}
;;  {:id 2, :name "Frozen", :price 3.0,  :promoted false, :genre :comedy}
;;  {:id 3, :name "Wall-E", :price 1.75, :promoted true,  :genre :comedy}
;;  {:id 4, :name "Avatar", :price 1.75, :promoted false, :genre :sci-fi}
;;  {:id 5, :name "Aliens", :price 0.5,  :promoted true,  :genre :sci-fi})

Filtering values based on conditions

;; Get only movies that are promoted
(filter (fn [movie] (true? (:promoted movie))) movies)
(filter #(true? (:promoted %)) movies)

;; ({:id 1, :name "Hobbit", :price 2.5, :promoted true, :genre :fantasy}
;;  {:id 3, :name "Wall-E", :price 3.5, :promoted true, :genre :comedy}
;;  {:id 5, :name "Aliens", :price 1.0, :promoted true, :genre :sci-fi})

Filtering by one condition is easy enough. Let’s take a look how to do it with two!

;; Get only movies that are promoted AND cost less than $2
(filter (fn [movie] (and (true? (:promoted movie)) (< (:price movie) 2))) movies)
(filter #(and (true? (:promoted %)) (< (:price %) 2)) movies)

;; ({:id 5, :name "Aliens", :price 1.0, :promoted true, :genre :sci-fi})

Where to practice writing functions

When I started learning Clojure I had trouble switching back and forth between (fn) and #(). In the beginning I often made the mistake of writing my anonymous function as (#()). This invokes the function.

Doing a lot of practice problems on 4clojure helped me get used to the syntax and practice core concepts. If you ever get stuck on a problem, I’ve shared my 4clojure solutions on Github.

Happy practicing!

© 2023 Silvia Pan