mapply for Clojure

Clojure
R
Published

2023-12-29


I’ve been an avid R user for approaching 20 years now. Primarily, I’ve used R for data science, engineering, visualisation and analysis, as well as numerical modelling, and for the most part I’ve really enjoyed the experience. Nevertheless, I have recently found myself using Clojure more and more often for tasks that I would have normally reached for R. While Clojure isn’t typically known for Data Science, there are an increasing number of great data-focused tools[1] and libraries[2], plus the simplicity and versatility of the language make it a lot of fun to use. Inevitably, though, there are times when I wish I had functions at my disposal that are standard within R. One such example is mapply.

[1] For example, Clerk offers a superb notebook experience.

[2] tech.ml.dataset, for instance, is a high performance library for tabular data and tablecloth adds a friendly API.

Many R functions are vectorised, meaning that if you pass vectors as arguments the function is automatically applied over each element of the vectors in turn. Take +, for example[3].

[3] While we typically use + as an infix operator, it is a function:

> class(`+`)
[1] "function"

Consequently, it can be called using prefix form like we do with other functions:

> `+`(1, 2)
[1] 3
> 1+1 # Single integers
[1] 2
> 1:10+11:20 # Two integer vectors - no additional syntax!
[1] 12 14 16 18 20 22 24 26 28 30

If a function is not vectorised, however, we need to iterate over each element individually and this is where mapply comes in. For example, if + was not vectorised then we could write the above code as follows using mapply[4]:

[4] It would be a fairly daft thing to do, though, as we’ve shown that + is vectorised and therefore mapply is entirely unnecessary.

> mapply(`+`, 1:10, 11:20)
[1] 12 14 16 18 20 22 24 26 28 30

This is the functionality that I’d like available in Clojure. As it happens, it is fairly straightforward to implement!

user=> (defn mapply [fun & vecs]
         (apply map fun vecs))

In Clojure, defn is used to define functions, followed by the function name and then square brackets containing the parameters. Beyond that is the body of the function with the final line producing the return value.

Here, we use map to apply the function fun over each sequence that follows: in this case, & vecs. The ampersand or rest parameter indicates that there could be any number of sequences passed to the function[5].

[5] If we’re feeling fancy, we could call it a variable-arity function.

But what does apply do in our mapply function? Well, if the number of arguments are not known at compile time — which is true here since mapply accepts any number of arguments — then apply is used to iterate over each argument that is passed. And we can see that mapply works as desired — bonza!

user=> (mapply + (range 1 11) (range 11 21))              
(12 14 16 18 20 22 24 26 28 30)
Back to top