Creating a function with both named and positional parameters
class Foo
def initialize(bar:, bang:, bamph, &block)
# ...
end
end
Produces a syntax error:
$ ruby -c scratch.rb
scratch.rb:2: syntax error, unexpected tIDENTIFIER
...f initialize(bar:, bang:, bamph, &block)
... ^~~~~
Wheras this
def initialize(bamph, bar:, bang:, &block)
# ...
end
does not.
As far as I can see, this answer explains that the ordering of the parameter type must follow a specific pattern. But what benefit is gained by enforcing this hierarchy?
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 Hashes 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:
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:
foo(**{}) outIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With