Keyword arguments in Clojure and Ruby and Common Lisp

(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: lawrence@krubner.com

This is a brilliant tour of how 3 languages handle their function arguments:

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.

Source