Keyword arguments in Clojure

Posted: August 13, 2012 in Uncategorized
Tags: , ,

Recently I’ve found myself using keyword arguments more and more in Clojure so this post is about how to use keyword arguments and their pros and cons.

The basic way of specifying keyword arguments to a function in Clojure is with the :keys feature in the argument list. It looks something like this:

(defn hello [& {:keys [salutation name]
                :or {salutation "Hello"
                     name "World"}}]
  (str salutation " " name))

=> "Hello World"

(hello :name "John")
=> "Hello John"

(hello :name "John" :salutation "Hi")
=> "Hi John"

Note the use of the or map to provide default argument values. This is named after the common Clojure idiom of doing (or input-value default-value) to provide a default value in case the expected input value is missing.

The alternative to this approach would typically be to provide a multiple-arity version of the function:

(defn greet
  ([] (greet "World"))
  ([name] (greet "Hello" name))
  ([salutation name]
    (str salutation " " name)))

=> "Hello World"

(greet "John")
=> "Hello John"

(greet "Hi" "John")
=> "Hi John"

So which should you use?

Pros of keyword arguments:

  • Gives flexibility to the user – you don’t need to provide the parameters in any particular order, and can skip parameters easily if you don’t need to provide them. This is much more convenient than multiple-arity functions when you have long argument lists.
  • Makes code more readable, as the keyword immediately tells you what the parameters are
  • Makes refactoring easier, as you can easily add a new keyword argument without breaking existing code
  • You can provide default arguments convenienty – via the or construct in the example code above. This is IMHO a bit more elegant than the approach of chaining calls between different function arities to provide default arguments.

Of course, there are also some downsides:

  • It’s a bit less concise. On average though, I don’t think this is a big deal. Clojure code is concise enough anyway, and readability / maintainability should be preferred to conciseness
  • There is some performance overhead to keyword arguments. By my rough tests, “hello” runs in about 550ns and “greet” runs in about 125ns given the two-argument versions above. So there’s about a 425ns overhead from handling the keyword arguments. Not a big overhead to be sure, but might be an issue in performance-critical code that is being run millions of times per second.
  • It encourages designing functions with too many arguments – which I think is generally a code smell. It suggests to me that a function is trying to do too much, or that it should be decomposed into simpler functions that can be then combined with higher order functions.

Overall though, I think keyword arguments are a very useful addition to the discerning Clojure hacker’s toolbox as long as they are used wisely.

  1. The time to run “greet” should be in ns, not ms.

  2. You have quoted ampersand sign in first example.

  3. Very nice post. I just stumbled upon your weblog and wished to say that
    I’ve truly enjoyed browsing your blog posts. In any case I’ll be subscribing to your feed and I hope you write again very soon!

    • Mike says:

      Glad you like it! I will indeed be posting more soon… as usual will be a fairly random mix of technical and creative stuff…

  4. Anthony says:

    Thanks, A very useful information .

  5. Jan says:

    I think you overlooked a critical drawback of keyword functions: they can’t be composed very well (i.e. how would you apply something like clojure.core/comp to keyword functions)! And some people would argue that function composition is a hallmark of functional programming.

  6. […] The use of keyword arguments in a Clojure argument list was shamelessly cribbed from “Creative Clojure: Keyword arguments in Clojure“. […]

  7. […] which is expected to be either true or false. It would be more idiomatic to make these keyword arguments, like […]

  8. […] There is no global configuration map or anything like that. It works because tasks accept only keyword arguments, so partial application is idempotent and last setting […]

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s