mapply for Clojure
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!
defn mapply [fun & vecs]
user=> (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!
+ (range 1 11) (range 11 21))
user=> (mapply 12 14 16 18 20 22 24 26 28 30) (