What (if any) are the rules for deciding the order of the parameters functions in Clojure core?
map
and filter
expect a data structure as the last
argument. assoc
and select-keys
expect a data
structure as the first argument.map
and filter
expect a function as the first
argument.update-in
expect a function as the last argument.This can cause pains when using the threading macros (I know I can use as->
) so what is the reasoning behind these decisions? It would also be nice to know so my functions can conform as closely as possible to those written by the great man.
Yes, it matters. The arguments must be given in the order the function expects them.
3. Positional Arguments. During a function call, values passed through arguments should be in the order of parameters in the function definition. This is called positional arguments.
First Class Functions They can be assigned as values, passed into functions, and returned from functions. It's common to see function definitions in Clojure using defn like (defn foo … ) . However, this is just syntactic sugar for (def foo (fn … )) fn returns a function object.
It doesn't matter what order they are given in the parameter so long as the arguments in the call expression match the correct variable. Either way won't matter to the output string.
Functions that operate on collections (and so take and return data structures, e.g. conj
, merge
, assoc
, get
) take the collection first.
Functions that operate on sequences (and therefore take and return an abstraction over data structures, e.g. map
, filter
) take the sequence last.
Becoming more aware of the distinction [between collection functions and sequence functions] and when those transitions occur is one of the more subtle aspects of learning Clojure.
(Alex Miller, in this mailing list thread)
This is important part of working intelligently with Clojure's sequence API. Notice, for instance, that they occupy separate sections in the Clojure Cheatsheet. This is not a minor detail. This is central to how the functions are organized and how they should be used.
It may be useful to review this description of the mental model when distinguishing these two kinds of functions:
I am usually very aware in Clojure of when I am working with concrete collections or with sequences. In many cases I find the flow of data starts with collections, then moves into sequences (as a result of applying sequence functions), and then sometimes back to collections when it comes to rest (via into, vec, or set). Transducers have changed this a bit as they allow you to separate the target collection from the transformation and thus it's much easier to stay in collections all the time (if you want to) by apply into with a transducer.
When I am building up or working on collections, typically the code constructing it is "close" and the collection types are known and obvious. Generally sequential data is far more likely to be vectors and conj will suffice.
When I am thinking in "sequences", it's very rare for me to do an operation like "add last" - instead I am thinking in whole collection terms.
If I do need to do something like that, then I would probably convert back to collections (via into or vec) and use conj again.
Clojure's FAQ has a few good rules of thumb and visualization techniques for getting an intuition of collection/first-arg versus sequence/last-arg.
Rather than have this be a link-only question, I'll paste a quote of Rich Hickey's response to the Usenet question "Argument order rules of thumb":
One way to think about sequences is that they are read from the left, and fed from the right:
<- [1 2 3 4]
Most of the sequence functions consume and produce sequences. So one way to visualize that is as a chain:
map<- filter<-[1 2 3 4]
and one way to think about many of the seq functions is that they are parameterized in some way:
(map f)<-(filter pred)<-[1 2 3 4]
So, sequence functions take their source(s) last, and any other parameters before them, and partial allows for direct parameterization as above. There is a tradition of this in functional languages and Lisps.
Note that this is not the same as taking the primary operand last. Some sequence functions have more than one source (concat, interleave). When sequence functions are variadic, it is usually in their sources.
I don't think variable arg lists should be a criteria for where the primary operand goes. Yes, they must come last, but as the evolution of assoc/dissoc shows, sometimes variable args are added later.
Ditto partial. Every library eventually ends up with a more order- independent partial binding method. For Clojure, it's #().
What then is the general rule?
Primary collection operands come first.That way one can write -> and its ilk, and their position is independent of whether or not they have variable arity parameters. There is a tradition of this in OO languages and CL (CL's slot-value, aref, elt - in fact the one that trips me up most often in CL is gethash, which is inconsistent with those).
So, in the end there are 2 rules, but it's not a free-for-all. Sequence functions take their sources last and collection functions take their primary operand (collection) first. Not that there aren't are a few kinks here and there that I need to iron out (e.g. set/ select).
I hope that helps make it seem less spurious,
Rich
Now, how one distinguishes between a "sequence function" and a "collection function" is not obvious to me. Perhaps others can explain this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With