Did you know about argument order?

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?“

The most important argument: first or last?

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!

A contradiction?

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.

Datastructures go first!

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.

But what about partial?

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]

And sequences?

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.

Upshot

When designing the API of your library and mulling over argument order you should adhere to Clojure's principles:

  • For functions on data structures, the data structure should go first.
  • For functions on sequences, the sequence should go last.

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.

Footnotes

  1. When Rich says, that variadic arguments should not play a role in the decision of where the most important argument goes, I think he refers to single functions, not the question in general. (Ie. for a function with variadic arguments it goes first, for another function working on the same datastructure with no variadic arguments it goes last.)
  2. Even Haskell code features sometimes a (`f` a), which is a rather cryptic way to say #(f % a), and which works only with dyadic functions.
  3. Just kidding. Of course this at most a very weak argument. If at all.

Published by Meikel Brandmeyer on .