(fnil f x) (fnil f x y) (fnil f x y z)
Takes a function f, and returns a function that calls f, replacing a nil first argument to f with the supplied value x. Higher arity versions can replace arguments in the second and third positions (y, z). Note that the function f can take any number of arguments, not just the one(s) being nil-patched.
;; a function that expects a non-nil value
(defn say-hello [name] (str "Hello " name))
;;=> #'user/say-hello
;; fnil lets you create another function with a default
;; arg in case it is passed a nil
(def say-hello-with-defaults (fnil say-hello "World"))
;;=> #'user/say-hello-with-defaults
;; the happy path works as you would expect
(say-hello-with-defaults "Sir")
;;=> "Hello Sir"
;; but in the case that the function is passed a nil it will use the
;; default supplied to fnil
(say-hello-with-defaults nil)
;;=> "Hello World"
;; this works with different arities too
(defn say-hello [first other] (str "Hello " first " and " other))
;;=> #'user/say-hello
;; lets create it with defaults
(def say-hello-with-defaults (fnil say-hello "World" "People"))
;;=> #'user/say-hello-with-defaults
;; call the function with all nil args - notice it uses the defaults
;; supplied to fnil
(say-hello-with-defaults nil nil)
;;=> "Hello World and People"
;; any of the args can be nil - the function will supply
;; the default supplied with fnil
(say-hello-with-defaults "Sir" nil)
;;=> "Hello Sir and People"
;; and again - notice that "World" is the default here
(say-hello-with-defaults nil "Ma'am")
;;=> "Hello World and Ma'am"
;; or pass all args
(say-hello-with-defaults "Sir" "Ma'am")
;;=> "Hello Sir and Ma'am"
;; Treat nil as 0 for the purposes of incrementing
((fnil inc 0) nil)
;;=> 1
;; While the following would not work:
(inc nil)
;;=> NullPointerException clojure.lang.Numbers.ops (Numbers.java:961)
;; fnil is very useful for specifying default values when updating maps
;; For a map containing counters of keys:
(update-in {:a 1} [:a] inc)
;;=> {:a 2}
;; Oops, our map does not have a key :b and update-in passes nil to inc
(update-in {:a 1} [:b] inc)
;;=> NullPointerException clojure.lang.Numbers.ops (Numbers.java:961)
;; But if we use fnil it works:
(update-in {:a 1} [:b] (fnil inc 0))
;;=> {:b 1, :a 1}
;; Another example is when map values are collections and we don't want
;; default behavior of conj with nil that produces a list
(conj nil 1)
;;=> (1)
;; I.e.
(update-in {} [:a] conj 1)
;;=> {:a (1)}
;; But say we want map values to be vectors instead:
(update-in {} [:a] (fnil conj []) 1)
;;=> {:a [1]}