Argument order rules of thumb

1894 views
Skip to first unread message

Stuart Sierra

unread,
Apr 14, 2008, 10:40:46 PM4/14/08
to Clojure
Hello everyone. I thought I'd take advantage of a quiet couple of
days to ... stir up some trouble. This is just a thought I had. Take
it or leave it, flame it or flog it, whatever you like.

I have trouble remembering the order of arguments in the Clojure built-
in functions. Things like cons/conj, assoc/find, etc. So I thought
about it a bit and came up with some "rules of thumb" that seem
natural:

1. Variable arg lists have to go last;
2. The "operand," i.e. the object primarily being "operated upon,"
goes last, to make partial application easier and nested expressions
easier to read;
3. If a function/macro takes a collection, the collection goes last.

Skimming through the API, it seems like most of the built-in functions
fit these rules, with a few exceptions:
conj contains? find get nth nthrest select-keys subs subvec with-meta
set/project

Maybe it's already too late to suggest breaking changes in the core
language, or maybe Clojure, unlike its data structures, is still
somewhat mutable. :) Whatever, it's just an idea.

-Stuart

Rich Hickey

unread,
Apr 15, 2008, 7:43:38 AM4/15/08
to Clojure


On Apr 14, 10:40 pm, Stuart Sierra <the.stuart.sie...@gmail.com>
wrote:
Hmmm... Good thing those weren't the rules or assoc/dissoc couldn't
have become variadic. I know it might seem arbitrary, but it's
(usually :) not.

A better characterization (but not a rule, no promises) might be that
the sequence functions take the seq last, and the collection functions
take the collection first.

I think with #() the partial application story is always easy - no
matter what the argument order you often want to bind args other than
the first.

Rich

Rich Hickey

unread,
Apr 17, 2008, 10:07:21 AM4/17/08
to Clojure
I seem to have quashed the dialog with my contribution :(

Maybe this explanation will be more constructive.

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

Stuart Sierra

unread,
Apr 18, 2008, 2:53:23 PM4/18/08
to Clojure
On Apr 17, 10:07 am, Rich Hickey <richhic...@gmail.com> wrote:
> I seem to have quashed the dialog with my contribution :(

Not at all. My original post was in the spirit of "throw stuff
against the wall and see what sticks." It didn't stick with anyone,
and that's cool with me.

> 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).

Thank you for this, it helps to know the reasoning behind things. The
question was prompted by a function I wrote in seq-utils.clj in
clojure-contrib:

(defn includes? [value coll]
(if (some (fn [x] (= x value)) coll)
true false))

I wanted to have 'value' first, but I was afraid that was inconsistent
with (contains? map key). But it might be ok if the convention is
sequences-last-collections-first.

Thanks,
-Stuart

Chouser

unread,
Apr 18, 2008, 3:06:52 PM4/18/08
to clo...@googlegroups.com
On Fri, Apr 18, 2008 at 2:53 PM, Stuart Sierra
<the.stua...@gmail.com> wrote:
> Thank you for this, it helps to know the reasoning behind things. The
> question was prompted by a function I wrote in seq-utils.clj in
> clojure-contrib:
>
> (defn includes? [value coll]
> (if (some (fn [x] (= x value)) coll)
> true false))

I know you're talking about more general issues, but since you posted
this particular example, you might be interested in this conversation:

http://n01se.net/chouser/clojure-log/2008-04-11.html

Which includes this snippet from Rich: (some #{1} [1 2 3])

That returns true because an item in the set (1) is in the collection [1 2 3].

--Chouser

Stuart Sierra

unread,
Apr 20, 2008, 12:41:49 AM4/20/08
to Clojure
On Apr 18, 3:06 pm, Chouser <chou...@gmail.com> wrote:
> I know you're talking about more general issues, but since you posted
> this particular example, you might be interested in this conversation:
>
> http://n01se.net/chouser/clojure-log/2008-04-11.html
>
> Which includes this snippet from Rich: (some #{1} [1 2 3])
>
> That returns true because an item in the set (1) is in the collection [1 2 3].

So sets are callable?! That's trippy, man. But it makes sense with
the callable maps & vectors. I love how Clojure is more "functional"
that most "functional languages." Thanks, Chouser.

-Stuart

Shark Xu

unread,
Jun 1, 2016, 7:56:39 AM6/1/16
to Clojure, richh...@gmail.com
I think the tradition is more general: abstract process which need missing process passed in(seems all high order functions under this category) should have missing process comes first as it's parameter.
This tradition not only limited to sequence parameter.

在 2008年4月17日星期四 UTC+8下午10:07:21,Rich Hickey写道:

Shark Xu

unread,
Jun 1, 2016, 8:13:26 AM6/1/16
to Clojure, richh...@gmail.com
Also can we consider that put some special argument in some special position.

The main problem data, which often be the criteria of function dispatch, similar to OO's method target object, can as first parameter or after the missing process parameter?
So we can have a consistent parameter order model:
  missing-process-param*, main-problem-data-param, additional-param*

With this model maybe we get better use of "->" to focus on & thread main problem:
E.g.
(->
  main-problem-data
  concrete-func-1
  (concrete-func-2 additional-param)
  ((partial abstract-func-1 missing-process-1 missing-process-2))
  ((partial abstract-func-2 missing-process) additional-param))



在 2008年4月17日星期四 UTC+8下午10:07:21,Rich Hickey写道:
Reply all
Reply to author
Forward
0 new messages