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.
> 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.
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.
> On Apr 14, 10:40 pm, Stuart Sierra <the.stuart.sie...@gmail.com>
> wrote:
> > 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.
> 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.
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).
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:
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.
<the.stuart.sie...@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:
> 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.