Clojure higher-order functions explained: fnil
Published: 2021-03-19
Checkout the index for the full series.
Source code
It’s better to read the source code first because source code don’t lie. Here’s the source of fnil in clojure 1.10.1:
(defn fnil
"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."
{:added "1.2"
:static true}
([f x]
(fn
([a] (f (if (nil? a) x a)))
([a b] (f (if (nil? a) x a) b))
([a b c] (f (if (nil? a) x a) b c))
([a b c & ds] (apply f (if (nil? a) x a) b c ds))))
([f x y]
(fn
([a b] (f (if (nil? a) x a) (if (nil? b) y b)))
([a b c] (f (if (nil? a) x a) (if (nil? b) y b) c))
([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) c ds))))
([f x y z]
(fn
([a b] (f (if (nil? a) x a) (if (nil? b) y b)))
([a b c] (f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c)))
([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c) ds)))))
Usages
Simply put, fnil patches functions to handle nil arguments. Usually this is because you are using functions that you do not maintain and want them to handle nil
s in your particular use cases rather than just barfing out NPEs.
Personally, fnil
has become increasingly handy ever since I come to be more liberal on passing nil
s as real values in the front-end presentation logic. (See my other article about nil busting here.)
Examples
There are a couple of great examples by the Clojure community on clojuredocs.org’s fnil page. Please go check it out for the examples!
My use cases
This is my growing list of use cases where I stumble upon and found fnil
useful ;)
Python defaultdict in Clojure
Being a more Object-oriented language than functional, the way Python deal with inserting default value in a hash-table (or dict
) through the object, defaultdict. Example:
>>> import collections
>>> d = collections.defaultdict(list)
>>> d['yellow'].append(1)
>>> d
defaultdict(<class 'list'>, {'yellow': [1]})
The more functional approach to the problem can be done using fnil
in Clojure. Instead of defining the new behavior of an object, just create a function that knows what to do with nil
:
(def conj* (fnil conj []))
(update {} :yellow conj* 1)
;; => {:yellow [1]}
;; Or, inline the function
(update {} :yellow (fnil conj []) 1)
;; => {:yellow [1]}
Credits to clojuredocs’s user Dimagog’s example. It was my initial inspiration.
Identity fallback
Again from my previous article about nil busting here:
(defn self-or [other] (fnil identity other))
(->> [nil nil nil nil nil nil nil nil nil nil nil nil nil "Batman!"]
(map (self-or "na"))
(clojure.string/join ", "))
Retrofitting clojure string functions
Most of the clojure.string
functions don’t like nil
s and will throw NPE at you. fnil
to the rescue!
(require '[clojure.string :as str])
(sort-by (fnil str/lower-case "") ["hi" nil "ho"])
;; => (nil "hi" "ho")