And suddenly I return to blogging, rising from the ashes like some kind of zombie phoenix. Turns out writing a book is a good absorber of time, like some sort of heavy-duty temporal paper towel. Now that I've gotten the terrible similes out of my system, let's talk about keyword arguments, one of my favorite features in any language that supports them.
Ruby, Clojure, and Common Lisp are all languages I enjoy to some degree, and they all have keyword arguments. Let's explore how keyword args differ in those languages.
Why keyword arguments?
Why are keyword arguments good?
- You can omit arguments.
- You can supply arguments in an arbitrary order.
- Arguments are labeled, so you know what argument means what.
Positional arguments require mentally lining up the 7th argument in your function call with the 7th argument in the function signature, and so on. Keyword arguments become more and more attractive the more arguments you have in your function signature.
Keyword arguments trade a bit of verbosity for added explicitness, clarity and reduced mental burden. (Kind of like Lisps do overall. Fancy that.)
Ruby
Ruby doesn't have special support for keyword arguments. But Ruby likes its hashes, so you can just pass one in as an argument to a function. As some syntax sugar, if you pass a "flat" list of :key => val pairs, Ruby slurps them all together and stuffs them into a hash for you.
def foo(arg)
p arg
end
foo({:x => 123}) # => {:x=>123}
foo(:x => 123) # => {:x=>123}
With even more added sugar, you can leave off the parens in Ruby function calls. So this is pretty common in Ruby:
foo :x => 123 # => {:x=>123}
How nice and punctuation-less. But then things get ugly. What about this?
foo {:x => 123}
That won't even compile. Ruby thinks {:x => 123} is a code block, and a bare key/value pair isn't valid syntax as the first thing in a code block. You need the parens. A bit unfortunate, but it gets worse...
def bar(arg1,arg2)
puts "#{arg1} #{arg2}"
end
bar :x => 123, :y => 456 # Runtime error
bar {:x => 123}, {:y => 456} # Won't compile
In the first example, all of the key/value pairs are slurped into one hash and end up in arg1. There's nothing left for arg2, so you get a "wrong number of arguments" exception. The second example won't even compile, of course, because again Ruby thinks {:x => 123} must be a code block with invalid syntax.
It gets worse if you change the argument list for bar slightly...
def bar2(arg1 = {}, arg2 = {})
puts "#{arg1} #{arg2}"
end
This way, you don't even have to supply any arguments. This is nice, if all of your argument are optional.
bar2 # => {} {}
Now suppose you want arg1 to be {:x => 123}, and arg2 to be {:y => 456}. You might naively try this:
bar2 :x => 123, :y => 456 # => {:x=>123, :y=>456} {}
Oops, it all got dumped into arg1, and now instead of a missing argument error, arg2 silently ends up with a default, empty hash. You have to explicitly pass in an empty value for arg1 so that everything is slurped into arg2.
bar2 {}, :x => 123, :y => 456 # => WRONG! Ruby thinks {} is a code block again.
bar2({}, :x => 123, :y => 456) # => {} {:x=>123, :y=>456}
So much for syntax sugar. You might think you'd be unlikely to find this kind of thing in the wild, but in Ruby on Rails for example, there are quite a few functions whose argument lists look exactly like this. One signature for link_to is:
link_to(body, url_options = {}, html_options = {})
So...
link_to "foo", :controller => :x # OK
link_to "foo", :controller => :x, :class => "css_class" # WRONG!
link_to "foo", {:controller => :x}, :class => "css_class" # OK
What about support for default arguments? We might want to say that if you didn't pass in an :x argument, we want it to have some default value. You might think this would work:
def baz(x = {:x => 123})
p x
end
But you would be sadly mistaken.
baz # => {:x=>123}
baz :y => 456 # => {:y=>456} ... oops
Ruby doesn't merge your keyword arguments into the map in the parameter list. It uses that map if you don't supply any arguments, otherwise your map replaces the default entirely. So to get default arguments, you need something like
def baz2(args = {})
args = {:x => 123}.merge(args)
p args
end
baz2 # => {:x=>123}
baz2 :x => 555 # => {:x=>555}
baz2 :y => 456 # => {:x=>123, :y=>456}
Kind of messy, but that's OK.
One last subtle ambiguity in Ruby is determining whether someone passed a nil argument for a keyword explicitly, or omitted a keyword entirely. It might make a difference in some circumstances.
def quux(args={})
p args.include? :x
args = {:x => nil}.merge(args)
p args
end
quux # true, {:x=>nil}
quux :x => nil # false, {:x=>nil}
Pretty messy, but such is life.
Clojure
Part of the fun of Lisps is lack of ambiguity. Everything is spelled out in all its parenthesized glory. In Clojure, when you call a function like (foo :x 123), Clojure requires you to specify what you want to happen with those arguments.
It used to be that Clojure didn't have much support for keyword args at all. Clojure did have support for allowing variable numbers of arguments to functions though. So in the beginning, people used to slurp all of their arguments together into a list, and then turn it into a map inside the function.
user> (defn foo [& args]
(let [args (apply hash-map args)]
(prn args)))
#'user/foo
user> (foo)
{}
user> (foo :x 123)
{:x 123}
user> (foo :x 123 :y 456)
{:y 456, :x 123}
That worked. It still works today. But nowadays there's a better way. Why not slurp your arguments directly into a hash-map?
user> (defn foo [& {:as args}]
(prn args))
#'user/foo
user> (foo)
nil
user> (foo :x 123)
{:x 123}
user> (foo :x 123 :y 456)
{:y 456, :x 123}
This is an example of destructuring. In this case, all of our arguments are slurped into a list (thanks to &), then this list is matched against our destructuring pattern, in this case {:as args}. :as says to take everything in the list, turn it into a map and give the resulting map the name args.
This isn't a special feature of defn. Destructuring works anywhere you're setting up bindings, for example in let, for, doseq etc. Like so:
user> (let [{:as args} (list :x 123 :y 456)] args)
{:y 456, :x 123}
It just so happens that & creates the list for you, out of the arguments you pass.
Destructuring does lots more than that though. We can immediately pull out the values for keywords we care about, so they'll be bound to names in our function body.
user> (defn foo [& {:keys [x y z]}]
(prn x y z))
#'user/foo
user> (foo :z 123 :x 456)
456 nil 123
It's certainly a bit more verbose than Ruby in the function signature, but it lacks ambiguity and it saves you some repetition in the function body.
Clojure's approach also has the benefit of specifying directly in the function signature which keywords you're expecting. In a smart editor, like Emacs, you get an indicator of what kinds of keywords you should be passing in. See at the bottom?

This is also available at the Clojure REPL via the doc function.
user> (doc foo)
-------------------------
user/foo
([& {:keys [a b c]}])
nil
nil
There's no better documentation than live, built-in documentation. There's nothing more distracting when programming than context shifts, and having to dig into a web browser to check a function signature is a huge mental page fault.
What about default values? Sure. You can use :or to specify defaults for some or all of your keywords. This works much more like I'd expect, compared to Ruby.
user> (defn foo [& {:keys [x y z] :or {x 1 y 2 z 3}}]
(prn x y z))
#'user/foo
user> (foo :y 555)
1 555 3
What about determining whether the user passed nil for a keyword or whether they omitted the keyword entirely? For that, you have to resort to testing the argument map for the existence of the key, which isn't fun, but at least it's possible.
user> (defn foo [& {:keys [x y z]
:or {x 1 y 2 z 3}
:as args}]
(prn x y z)
(prn args)
(doseq [k [:x :y :z]]
(println "Contains" k "=>" (contains? args k))))
#'user/foo
user> (foo :x 1 :y 2)
1 2 3
{:x 1, :y 2}
Contains :x => true
Contains :y => true
Contains :z => false
Why do I keep mentioning this? See Common Lisp below.
In any case, our parameter list is becoming huge and unweildy. The first time I saw sample Clojure code like this, I almost did a spit-take. But after a bit of getting used to, I'm finding that this syntax is pretty comfortable.
The simplest case may not be as concise as Ruby, but aside from lack of ambiguity, the benefit of Clojure's approach is being able to safely do powerful (and borderline insane) things, like nested destructuring. And this works everywhere you're setting up a binding.
(defn ow-my-eyes [a & {[b {:keys [x y]}
& {[g h] :r
:keys [p q]
:or {q 123}}]
:x}]
(prn a b g h p q x y))
user> (ow-my-eyes 444 :x [1 {:x 555 :y 666} :p 3 :r [888 999]])
444 1 888 999 3 123 555 666
If you wrote code like that in real life, you'd likely be defenestrated, but at least you know you can do it. And destructuring in Clojure could likely be extended even further in the future, if someone came up with a use case for something that isn't supported.
Official documentation for all of this is here.
Common Lisp
When it comes to keyword arguments, Common Lisp supports mostly everything that Clojure does, and some things it doesn't. CL was likely a big inspiration for Clojure's destructuring. I highly recommend reading Practical Common Lisp to learn more about CL keyword arguments and list destructuring. (Read the rest of the book while you're at it.)
Keyword arguments in CL look like this:
> (defun foo (&key x (y 123) (z 456 z-supplied-p))
(pprint (list x y z z-supplied-p)))
FOO
> (foo)
(NIL 123 456 NIL)
> (foo :z nil) ; note, z-supplied-p tells us whether z was omitted or not
(NIL 123 NIL T)
> (foo :z 555 :x 666 :y 777)
(666 777 555 T)
> (foo :x 123)
(123 123 456 NIL)
> (foo :x 555)
(555 123 456 NIL)
Yeah, there's direct support for distinguishing supplied keys with nil values, and un-supplied keys. That's pretty nice.
Common Lisp also supports insane things like having keyword arguments' default values be a function of other arguments in the parameter list.
> (defun bar (&key (x 123) (y (+ x 1000)))
(pprint (list x y)))
BAR
> (bar)
(123 1123)
> (bar :x 5)
(5 1005)
> (bar :x 5 :y 6)
(5 6)
Clojure destructuring can't do this by default, so you'd have to resort to a let in the function body.
(defn bar [& {:keys [x y] :or {x 123}}]
(let [y (or y (+ x 1000))]
(prn x y)
There's nothing stopping Clojure's destructuring from being patched to support this, of course. But this isn't a feature I've ever found myself wanting particularly badly.
As for how to destructure CL keyword arguments into a vector or hash-map, CL doesn't directly support doing it in function parameter lists like Clojure does. (Though there's nothing stopping someone from throwing together a reader macro to let CL do this, of course.) This isn't really surprising, because CL loves its cons cells, while Clojure embraces maps (and vectors and sets etc.), offering them default reader syntax and lots of other built-in support.
So there you have it. Keyword arguments. Use them. Love them.
Lessons:
- Sugar can be bad for your health.
- Ambiguous, bad. Explicit, good.