26 October 2021

Clojure's 'comp' and 'juxt' functions

Just a few illustrative examples.

There are many pairs of related functions in Clojure (first/last, assoc/dissoc, conj/disj, etc...). Up until recently I had the impression that comp and juxt were related or were somehow the inverse of one another, but after working with them for a bit I don't see them that way anymore. Nevertheless, they are powerful tools.

comp

Consider the following snippet:

user=> (apply str (reverse (str/upper-case (str/trim " hello, world!"))))
"!DLROW ,OLLEH"

You might be tempted to transform this to a threading operation:

user=> (->> " hello, world!" str/trim str/upper-case reverse (apply str))
"!DLROW ,OLLEH"

Well, you could also use comp to define a function that has the same effect:

user=> ((comp (partial apply str) reverse str/upper-case str/trim) " hello, world!")
"!DLROW ,OLLEH"

So, sort of a reverse-threading-function-compopser.

juxt

Now for something that looks similar at first glance but is really quite different. The juxt function gathers a group of functions into a new function that passes its input to each of the functions.

Suppose we have a listing of [x y] coordinates:

user=> (def points [[2 4] [1 4] [4 4] [5 1] [1 5] [4 3]])

We'd like to sort the them by y value, then by x value:

([5 1] [4 3] [1 4] [2 4] [4 4] [1 5])

So, for sort to do that we basically need the y value to appear before the x value:

user=> points
[[4 4] [4 3] [2 4] [5 1] [1 4] [1 5]]
user=> (map (juxt last first) points)
([4 4] [3 4] [4 2] [1 5] [4 1] [5 1])

That does the trick. Now we can plug the function created by juxt into sort-by:

user=> (sort-by (juxt last first) points)
([5 1] [4 3] [1 4] [2 4] [4 4] [1 5])

Of course, in this case, we could just use (comp vec reverse):

user=> (sort-by (comp vec reverse) points)
([5 1] [4 3] [1 4] [2 4] [4 4] [1 5])

...but that's just because there are only 2 values in each element in this example. Suppose you had elements with more values and you wanted to sort by the 3rd value, then the 4th, or something. Then you'd need the juxt-based solution.