Last week, I had the chance of attending the Clojure dojo at the Thoughtworks office in London. It was great to see so many people enjoying to work with Clojure. Unfortunately chances are rather low to put up something similar here in the Rhein-Main region. sigh
However, what I really want to talk about is a question which arose during the dojo. In fact this question has been raised several times and I think it can't hurt to shed some more light on this issue: „How to order the arguments of your functions?“
Where should the „most important“ argument go? First or last? While it is not
always well-defined what „most important“ might mean, there are also many
cases where it is obvious – assoc
, conj
, select-keys
come to mind.
So the primary argument comes first, right? Hmm… Maybe not. Think about map
,
filter
or some
. There the most important arguments are the sequences. But
they come last!
How can we resolve this contradiction? I think the best way is to consult the one and only authoritative source for such information concerning Clojure: Rich Hickey himself. I think he gives a pretty good overview of how things are supposed to work in Clojure.
In Clojure the datastructure argument goes first. This allows eg. easy use of variadic arguments[1]. And as Rich states: using always the same ordering independent of the variadic nature of the function brings consistency.
Having it always first also opens the door to googies like ->
and Clojure's
update functions. swap!
, send
, alter
, update-in
– they all allow a
consistent look'n'feel, when the datastructure is the first argument.
(swap! map-in-atom update-in [:a :b :c] assoc :x 1 :y 2 :z 3)
It is simply beautiful how the cogs of swap!
, update-in
and assoc
work
together in such a concise way. Also note how swap!
and update-in
basically take the same form of interface for a similar task.
Proponents of point-free style often complain that this doesn't play well with
partial
. In fact in the dojo it was suspected that Clojure hasn't settled on
this question, because there are ->
, ->>
and partial
(with 2:1 for
„last“).
However again – as Rich's states: #()
simply takes care of any problems you
might have with partial
[2].
#(assoc % :a 1)
is even shorter than (a hypothetical) (partial assoc :a 1)
.
[3]
But – you might ask – why is it not the same with the sequence functions. And I'd probably answer with: „Why should it be the same?“ Sequences are not data structures! Why should the same rules apply?
Sequences are a description, which tells you how to get the first item of it (if any) and how to get the description for possibly more items. This is something fundamentally different to a data structure!
In particular is a sequence not a list. A list is something concrete, although it happens to implement the sequence abstraction directly. However it can only be finite. So every list is a sequence, but not every sequence a list.
A different world might have different rules.
When designing the API of your library and mulling over argument order you should adhere to Clojure's principles:
Even if you have different preferences, your library will fit better into the consistent Clojure world. Making your library easier accessible for new users.
And as a side-effect in our otherwise pure blog, we saw to today, that sequences are a completely different species than – say – a vector or a map.
(`f` a)
, which
is a rather cryptic way to say #(f % a)
, and which works only with dyadic
functions.Published by Meikel Brandmeyer on .
I'm a long-time Clojure user and the developer of several open source projects mostly involving Clojure. I try to actively contribute to the Clojure community.
My most active projects are at the moment VimClojure, Clojuresque and ClojureCheck.
Copyright © 2009-2014 All Right Reserved. Meikel Brandmeyer