Keyword arguments are easier in Clojure than in Ruby

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

Interesting:

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.

Post external references

  1. 1
    http://briancarper.net/blog/579/keyword-arguments-ruby-clojure-common-lisp
Source