Clojure higher-order functions explained: complement

2021 March 23

Table of Contents

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})
Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License.