Keyword parameters and arguments are relatively new in Ruby. They were only introduced in Ruby 2.0.
Before Ruby had keyword parameters and arguments, there was a widely-used idiom of passing a Hash
literal as the last argument of the method. This idiom looked something like this:
DEFAULTS = {
:mode => 'w',
:eol => :crlf,
}
def open_file(name, options = {})
raise ArgumentError unless options[:encoding]
options = DEFAULTS.merge(option)
mode, eol, encoding = options[:mode], options[:eol], options[:encoding]
# do your thing
end
open_file('test.txt', { :mode => 'r', :encoding => 'UTF-8' })
In order to make it look a bit more "keyword-like", you are allowed to leave out the parentheses if you pass a Hash
literal as the very last argument of a message send:
open_file('test.txt', :mode => 'r', :encoding => 'UTF-8')
In Ruby 1.9, an alternative syntax for a limited subset of Hash
literals was introduced: when the key is a Symbol
that is also a valid Ruby identifier (e.g. :foo
, but not :'foo-bar'
), then you can write it like this:
{ foo: bar }
instead of
{ :foo => bar }
So, we could call our method from above like this:
open_file('test.txt', { mode: 'r', encoding: 'UTF-8' })
and, since the rule about leaving out parentheses still applies, also like this:
open_file('test.txt', mode: 'r', encoding: 'UTF-8')
This looks very much like keyword arguments in other languages. In fact, this alternative literal syntax for Hash
es with Symbol
keys was at least partially specifically designed to provide a transition path for introducing keyword parameters and arguments into Ruby.
In Ruby 2.0, optional keyword parameters with default keyword arguments were introduced:
def open_file(name, mode: 'w', eol: :crlf, encoding: nil)
raise ArgumentError unless encoding
# do your thing
end
Then in Ruby 2.1 mandatory keyword parameters and arguments:
def open_file(name, mode: 'w', eol: :crlf, encoding:)
# do your thing
end
As you probably know, calling this method looks exactly like it did before:
open_file('test.txt', mode: 'r', encoding: 'UTF-8')
Note, however, that you can no longer tell what this means! You cannot know whether mode: 'r', encoding: 'UTF-8'
is a Hash
literal or two keyword arguments (in other words, you don't even know whether this is one or two arguments!) without looking at the definition of the method you are calling.
It was decided that Ruby 2.0 should be maximally backwards and forwards compatible with Ruby 1.9.
Therefore, all of the following must be true:
- A method that is defined with an options hash and is called with an options hash must still work.
- A method that is defined with an options hash and is called with keyword arguments must still work.
- A method that is defined with keyword parameters and is called with an options hash literal must still work.
In order to make all of this work, there are a lot of implicit conversions between hash literals and keyword arguments. Getting this to work without nasty corner cases is just much easier if keyword parameters and keyword arguments are only allowed to appear where the "fake" keyword syntax was allowed before, i.e. at the very end of the parameter list and argument list.
Actually, there are still a lot of nasty corner cases caused by this "blurring the lines" between hashes and keyword parameters. If you look through the Ruby issue tracker, you will find that a significant portion of issues reported since Ruby 2.0 are related to un-intuitive or simply buggy behavior in this regard. Every new release brings new changes, but one gets the feeling that for every hole they patch, they create two new ones.
Now, just imagine what it would be like if the rules were even less strict!
Here are some examples of those afore-mentioned issues: