Clojure higher-order functions explained: complement
Published: 2021-03-23
Checkout the index for the full series.
Source code
Here’s the source of complement in clojure 1.10.1:
(defn complement
"Takes a fn f and returns a fn that takes the same arguments as f,
has the same effects, if any, and returns the opposite truth value."
{:added "1.0"
:static true}
[f]
(fn
([] (not (f)))
([x] (not (f x)))
([x y] (not (f x y)))
([x y & zs] (not (apply f x y zs)))))
Usages
From the doc-string:
Takes a fn f and returns a fn that takes the same arguments as f, has the same effects, if any, and returns the opposite truth value.
Although there’s no restriction on the number of arguments, I found complement
to be most useful to create predicate functions, which take a single argument and return a value that meant to be consumed as a logical value.
A small caveat when using complement
is that it returns a Boolean instead of the data type from the original function f. This makes it a bit slightly inconvenient to use with keep
or some
if you expect the return value to be the inverse of the original function f:
;; some returns the first logical truthy value
(some #{:a :b} [:1 :2 :a :b])
;; => :a
;; true is a truthy value
(some (complement #{:a :b}) [:1 :2 :a :b])
;; => true
;; keep removes nil
(keep #{:a :b} [:1 :2 :a :b])
;; => (:a :b)
;; doesn't remove the false
(keep (complement #{:a :b}) [:1 :2 :a :b])
;; => (true true false false)
;; better to use remove instead
(remove #{:a :b} [:1 :2 :a :b])
;; => (:1 :2)
filter
on the other hand is the most consistent (the least surprising) one with complement
:
(filter #{:a :b} [:1 :2 :a :b])
;; => (:a :b)
(filter (complement #{:a :b}) [:1 :2 :a :b])
;; => (:1 :2)
Examples
There are a couple of great examples by the Clojure community on clojuredocs.org’s complement 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 complement
useful ;)
Not contains?
Already mentioned above, not-contain can be written as:
(filter #(not (contains? #{:a :b} %)) [:1 :2 :a :b])
(filter #(not (#{:a :b} %)) [:1 :2 :a :b])
(filter (complement #{:a :b}) [:1 :2 :a :b])
Not :key
(filter :disabled [{:v 1} {:v 2} {:v 3 :disabled true} {:v 4}])
;; => ({:v 3, :disabled true})
(filter (complement :disabled) [{:v 1} {:v 2} {:v 3 :disabled true} {:v 4}])
;; => ({:v 1} {:v 2} {:v 4})