keyword parameters?

1,140 views
Skip to first unread message

Robin

unread,
Jan 3, 2008, 9:02:36 PM1/3/08
to Clojure
Can Clojure do Lisp-like keyword params with defn cleaner than this?

user=> (defn foo [#^IPersistentMap kv] (list (kv :a) (kv :b) (kv :c)))
#<Var: user/foo>
user=> (foo {:a 1 :c 3})
(1 nil 3)

It might look something like this:

user=> (defn foo [&key a b c] (list a b c))
#<Var: user/foo>
user=> (foo :a 1 :c 3)
(1 :c 3) ;;;doesn't work as intended

Thanks

Chas Emerick

unread,
Jan 3, 2008, 10:33:23 PM1/3/08
to Clojure
Robin,

No, Clojure does not provide keyword params. From what I gather, this
is done for efficiency, keeping Clojure's function application
procedure as close to Java's as possible (i.e. avoiding runtime
argument binding, etc).

Cheers,

- Chas

Rich Hickey

unread,
Jan 3, 2008, 11:00:07 PM1/3/08
to Clojure
I've decided against keyword params for Clojure. I had an
implementation in prototypes for a while. I think what is needed now
is a destructuring-bind that makes it easy to grab pieces of maps when
passed as args.

Rich

On Jan 3, 9:02 pm, Robin <robi...@gmail.com> wrote:

Jonathan T

unread,
Jan 15, 2008, 10:44:53 PM1/15/08
to Clojure
Instead of destructuring-bind, what about pattern matching? I'm going
to end up implementing this myself eventually anyway. It would be
nice if there was a standard way of doing it in Clojure.

Rich Hickey

unread,
Jan 16, 2008, 11:51:13 AM1/16/08
to Clojure


On Jan 15, 10:44 pm, Jonathan T <jonnyt...@gmail.com> wrote:
> Instead of destructuring-bind, what about pattern matching? I'm going
> to end up implementing this myself eventually anyway. It would be
> nice if there was a standard way of doing it in Clojure.
>

If you are talking about the aspect of pattern matching that acts as a
conditional based upon structure, I'm not a big fan. I feel about them
the way I do about switch statements - they're brittle and
inextensible. I find the extensive use of them in some functional
languages an indication of the lack of polymorphism in those
languages. Some simple uses are fine - i.e. empty list vs not, but
Clojure's if handles that directly. More complex pattern matches end
up being switches on type, with the following negatives:

They are closed, enumerated in a single construct.
They are frequently replicated.
The binding aspect is used to rename structure components because the
language throws the names away, presuming the types are enough. Again,
redundantly, all over the app, with plenty of opportunity for error.

Polymorphic functions and maps are, IMO, preferable because:

They are open, no single co-located enumeration of possibilities.
There is no master switch, each 'type' (really situation, with
Clojure's multimethods) owner takes care of themselves.
Maps allow for structures with named components where the names don't
get lost.

I'd much rather use (defstruct color :r :g :b :a) and end up with maps
with named parts than do color of real*real*real*real, and have to
rename the parts in patterns matches everywhere (was it rgba or argb?)

I'm not saying pattern matching is wrong or has no utility, just that
I would hope it would be less needed in idiomatic Clojure code.
Certainly I'd want the binding part of pattern matching independent of
the conditional part, and I would make any destructuring-bind for
Clojure work with all of the data structures, not just lists, i.e.
vectors and maps and metadata.

If you, or anyone else, were to contribute a destructuring-bind and/or
pattern matching macro written in Clojure I'm sure it would be
appreciated by many, as it's not something I'm likely to get to soon.

Rich

Jonathan Tran

unread,
Jan 17, 2008, 2:02:43 AM1/17/08
to clo...@googlegroups.com
I see your point about multimethods and maps. ...I guess I'm still in
the process of wrapping my head around what makes Clojure Clojure.

Mark H.

unread,
Jan 17, 2008, 5:48:22 AM1/17/08
to Clojure
On Jan 16, 8:51 am, Rich Hickey <richhic...@gmail.com> wrote:
> On Jan 15, 10:44 pm, Jonathan T <jonnyt...@gmail.com> wrote:
>
> > Instead of destructuring-bind, what about pattern matching? I'm going
> > to end up implementing this myself eventually anyway. It would be
> > nice if there was a standard way of doing it in Clojure.
>
> If you are talking about the aspect of pattern matching that acts as a
> conditional based upon structure, I'm not a big fan.

You may want to check out "Purely Functional Data Structures" -- see

http://www.eecs.usma.edu/webs/people/okasaki/pubs.html

-- and look at the neat stuff people have done with these ideas in CL.
There's a red-black tree example which is particularly elegant. (Any
kind of dynamic tree rebalancing involves pattern matching on
structure, for example, regardless of whether you have a syntax to
support pattern matching or not.)

> the conditional part, and I would make any destructuring-bind for
> Clojure work with all of the data structures, not just lists, i.e.
> vectors and maps and metadata.

METABANG-BIND is a good example of such a package in CL. I
imagine it could be adapted without too much trouble.

mfh

Rich Hickey

unread,
Jan 17, 2008, 10:40:56 AM1/17/08
to Clojure


On Jan 17, 5:48 am, "Mark H." <mark.hoem...@gmail.com> wrote:
> On Jan 16, 8:51 am, Rich Hickey <richhic...@gmail.com> wrote:
>
> > On Jan 15, 10:44 pm, Jonathan T <jonnyt...@gmail.com> wrote:
>
> > > Instead of destructuring-bind, what about pattern matching? I'm going
> > > to end up implementing this myself eventually anyway. It would be
> > > nice if there was a standard way of doing it in Clojure.
>
> > If you are talking about the aspect of pattern matching that acts as a
> > conditional based upon structure, I'm not a big fan.
>
> You may want to check out "Purely Functional Data Structures" -- see
>
> http://www.eecs.usma.edu/webs/people/okasaki/pubs.html
>

> -- and look at the neat stuff people have done with these ideas in CL.
> There's a red-black tree example which is particularly elegant. (Any
> kind of dynamic tree rebalancing involves pattern matching on
> structure, for example, regardless of whether you have a syntax to
> support pattern matching or not.)
>

I've read Okasaki. You might want to check out PersistentTreeMap.java,
in the Clojure source, as it is an implementation of Okasaki's
persistent red-black tree in Java, and is what you get back from
sorted-map. Also PersistentQueue.java (not yet exposed) is based on
Okasaki's batched queues, but not amortized, as I have persistent
vectors and a constant time way to turn them into sequences.

I won't argue for the verbosity of Java vs. SML or Haskell, and I
readily acknowledge the beauty of the original formulation (although
the remove code, not in the book, isn't pretty in any language). But
it was an interesting exercise to move code from a pattern-matching
language to a non-pattern-matching language. This was my experience:

I started with pretty much a transliteration of the original,
replacing datatype tags with enums and matches with conditionals. The
result was none too fast. Also, I wanted to optimize the memory use,
as I knew many nodes would be non-branching leaves, and often maps
would be used as sets, i.e. without values. (Also, at that time I
didn't have persistent hash maps, and thought the rb tree would have
to serve all the purposes for which hash maps now serve). One could
imagine extending the Tree ADT (of the SML version) from E|T to E|T|
Leaf|EmptyT|EmptyLeaf or some such, but, without polymorphism, the
effect on the pattern matching is a multiplicative growth of
combinations.

So, given I was in Rome (Java), I decided to do the idiomatic thing,
and pursued a hybrid strategy, maximizing the use of polymorphism,
(which I know is optimized in Java), and minimizing the use of
conditionals. (The remove code is still pretty much conditionals, as
the logic is too impenetrable to safely recast :)

The result is the hierarchy of Nodes, implementing the memory usage
strategy, and, for add/lookup at least, mostly polymorphic method
dispatch. The result is more efficient and _much_ faster. The logic is
distributed - each node type takes care of itself to some degree. It
was, IMO, easier to add the with/without value/branches variations
this way to maintain a conditional approach, with or without patterns.
The result might seem a bit verbose, but comparing it to the Okasaki
book example is not apples-to-apples. You would have to have an SML/
Haskell version that:

Was a map, not just a set.
Had the remove code.
Had optional values, and used no storage for no value.
Didn't store branches for leaves.
Allowed the option of comparing elements _or_ supplying a comparator.

I'm sure all of this is elegantly possible, but it won't be the pretty
little thing in the book anymore.

Here are some of my takeaways:

- Adding types can have a multiplicative, rather than an additive,
effect on pattern matches.
- Without a pattern-match optimizer, naive mechanical translation of
patterns to if-else constructs can produce slow code.

The latter is particularly important. I'm sure SML/Haskell/Erlang
optimize pattern matching, and while it may be easy to mimic the
superficial behavior with a macro, the optimizers are likely non-
trivial.

> > the conditional part, and I would make any destructuring-bind for
> > Clojure work with all of the data structures, not just lists, i.e.
> > vectors and maps and metadata.
>
> METABANG-BIND is a good example of such a package in CL. I
> imagine it could be adapted without too much trouble.
>

Given that Clojure has vector and map literals, I would hope to use
them to match their counterparts, so it will differ in at least that
respect.

Rich

Mark H.

unread,
Jan 20, 2008, 1:59:49 PM1/20/08
to Clojure
On Jan 17, 7:40 am, Rich Hickey <richhic...@gmail.com> wrote:
> I've read Okasaki.

Haha, I think this was one of those "hit send first, think later"
moments
for me ;-) I should have realized you would not only be aware of
these
ideas, but would of necessity be well-versed in them!

> - Adding types can have a multiplicative, rather than an additive,
> effect on pattern matches.
> - Without a pattern-match optimizer, naive mechanical translation of
> patterns to if-else constructs can produce slow code.
>
> The latter is particularly important. I'm sure SML/Haskell/Erlang
> optimize pattern matching, and while it may be easy to mimic the
> superficial behavior with a macro, the optimizers are likely non-
> trivial.

That's actually an interesting question -- how much optimization do
typical SML/Haskell/Erlang compilers do on pattern matching? But
somehow I suspect that they won't get very far with the kind of
matching code that does things like red-black trees.

Anyhow, I will stop weighing in on subjects for which I'm much less
an expert than the main developer! ;-)

mfh

John Cowan

unread,
Jan 20, 2008, 3:33:38 PM1/20/08
to clo...@googlegroups.com
On Jan 20, 2008 1:59 PM, Mark H. <mark.h...@gmail.com> wrote:

> On Jan 17, 7:40am, Rich Hickey <richhic...@gmail.com> wrote:
> > I've read Okasaki.

[snip]

> That's actually an interesting question -- how much optimization do
> typical SML/Haskell/Erlang compilers do on pattern matching? But
> somehow I suspect that they won't get very far with the kind of
> matching code that does things like red-black trees.

Speaking of Okasaki and red-black trees, I just read his little paper
on them, and it's just amazing how he manages to reduce the normal
four rotations to a single transformation to be done if a newly
inserted node (colored red by default) has a red parent:

"Take the red child, the red parent, and the (black) grandparent and
locally balance these three nodes by making the smallest and largest
nodes children of the middle node. Then color the middle node red and
the other two nodes black. The middle node is linked back into the
tree in place of the former black grandparent."

If there is still a node with a red parent, then repeat the
transformation, and if you reach the root, color the root black
unconditionally (red nodes have to have black parents, so the root
can't be red).

See http://www.eecs.usma.edu/webs/people/okasaki/sigcse05.pdf , a
perfect gem which gives two pages to R/B trees and two to maxiphobic
heaps, an easier-to-understand alternative to leftist heaps.

--
GMail doesn't have rotating .sigs, but you can see mine at
http://www.ccil.org/~cowan/signatures

timmy

unread,
Jan 25, 2008, 7:05:31 PM1/25/08
to Clojure


On Jan 4, 5:00 am, Rich Hickey <richhic...@gmail.com> wrote:
> I've decided against keyword params for Clojure. I had an
> implementation in prototypes for a while. I think what is needed now
> is a destructuring-bind that makes it easy to grab pieces of maps when
> passed as args.

Hello,

i wrote a macro which provides keywords for defn forms. As you say,
there is (a lot of) overhead at runtime involved when parsing the
arguments but its worth the additional complexity for say, constructor-
like functions, especially when interfacing with huge java libraries
like swing.
I also wrote a simple hash-bind and vector-bind and discovered that
the latter is something like multiple-values in common-lisp (and very
useful, of course).

The sources are on www.turbojugend-cossebaude.de/clojure/.

btw., thank you much for the work on clojure, i really enjoy that kind
of functional programming with java libraries at hand.

erik

Rich Hickey

unread,
Jan 25, 2008, 8:37:36 PM1/25/08
to Clojure


On Jan 25, 7:05 pm, timmy <i_am_wea...@kittymail.com> wrote:
> On Jan 4, 5:00 am, Rich Hickey <richhic...@gmail.com> wrote:
>
> > I've decided against keyword params for Clojure. I had an
> > implementation in prototypes for a while. I think what is needed now
> > is a destructuring-bind that makes it easy to grab pieces of maps when
> > passed as args.
>
> Hello,
>
> i wrote a macro which provides keywords for defn forms. As you say,
> there is (a lot of) overhead at runtime involved when parsing the
> arguments but its worth the additional complexity for say, constructor-
> like functions, especially when interfacing with huge java libraries
> like swing.
> I also wrote a simple hash-bind and vector-bind and discovered that
> the latter is something like multiple-values in common-lisp (and very
> useful, of course).
>
> The sources are on www.turbojugend-cossebaude.de/clojure/.
>

That's neat. Would you consider putting a license on it, preferably
CPL, so that others can use it? Without one I can't really look at it.

> btw., thank you much for the work on clojure, i really enjoy that kind
> of functional programming with java libraries at hand.
>

You're welcome. Glad to see you are productive with Clojure.

Rich

timmy

unread,
Jan 26, 2008, 1:50:01 AM1/26/08
to Clojure

On Jan 26, 2:37 am, Rich Hickey <richhic...@gmail.com> wrote:

> That's neat. Would you consider putting a license on it, preferably
> CPL, so that others can use it? Without one I can't really look at it.

of course, fixed that, i put it under the CPL.

erik

Rich Hickey

unread,
Jan 29, 2008, 8:06:08 PM1/29/08
to Clojure


On Jan 25, 7:05 pm, timmy <i_am_wea...@kittymail.com> wrote:
> On Jan 4, 5:00 am, Rich Hickey <richhic...@gmail.com> wrote:
>
> > I've decided against keyword params for Clojure. I had an
> > implementation in prototypes for a while. I think what is needed now
> > is a destructuring-bind that makes it easy to grab pieces of maps when
> > passed as args.
>
> Hello,
>
> i wrote a macro which provides keywords for defn forms. As you say,
> there is (a lot of) overhead at runtime involved when parsing the
> arguments but its worth the additional complexity for say, constructor-
> like functions, especially when interfacing with huge java libraries
> like swing.

It need not be too complex. I think a lot of the complexity comes from
trying to support keys & rest, which is a rare combination (Ken
Anderson once analyzed a Lisp app with ~27000 functions and macros and
found only ~200 &keys &rest cases). Without that, it can be something
like this:

(defn keyword? [x]
(instance? clojure.lang.Keyword x))

(defmacro defnk [sym args & body]
(let [pos-keys (split-with (complement keyword?) args)
ps (pos-keys 0)
ks (apply array-map (pos-keys 1))
gkeys (gensym "gkeys__")
letk (fn [ke]
(let [k (key ke)
kname (symbol (name k))
v (val ke)]
`(~kname (if (contains? ~gkeys ~k) (~gkeys ~k) ~v))))]
`(defn ~sym [~@ps & k#]
(let [~gkeys (apply hash-map k#)
~@(apply concat (map letk ks))]
~@body))))

(defnk foo [a b c :d 1 :e 2] [a b c d e])

(foo 3 4 5 :e 7)
-> [3 4 5 1 7]

And it's easy enough to apply hash-map to a rest arg, as does the
latest refer.

Rich

Reply all
Reply to author
Forward
0 new messages