Named Parameters in Ruby 2

Named parameters in Ruby 2

The last example you posted is misleading. I disagree that the behavior is similar to the one before. The last example passes the argument hash in as the first optional parameter, which is a different thing!

If you do not want to have a default value, you can use nil.

If you want to read a good writeup, see "Ruby 2 Keyword Arguments".

Can I have required named parameters in Ruby 2.x?

There is no specific way in Ruby 2.0.0, but you can do it Ruby 2.1.0, with syntax like def foo(a:, b:) ...

In Ruby 2.0.x, you can enforce it by placing any expression raising an exception, e.g.:

def say(greeting: raise "greeting is required")
# ...
end

If you plan on doing this a lot (and can't use Ruby 2.1+), you could use a helper method like:

def required
method = caller_locations(1,1)[0].label
raise ArgumentError,
"A required keyword argument was not specified when calling '#{method}'"
end

def say(greeting: required)
# ...
end

say # => A required keyword argument was not specified when calling 'say'

How do I add named parameters in a subclass or change their default in Ruby 2.2?

The problem here is that since the parent doesn't know anything about the child's arguments, it has no way of knowing whether the first argument you pass to it was meant to be a positional argument, or whether it was intended to provide keyword arguments to the parent method. This is because of a historical feature where Ruby allowed hashes to be passed as keyword-argument style parameters. For example:

def some_method(options={})
puts options.inspect
end

some_method(arg1: "Some argument", arg2: "Some other argument")

Prints:

{:arg1=>"Some argument", :arg2=>"Some other argument"}

If Ruby disallowed that syntax (which would break backwards compatibility with existing programs), you could write your child method like this using the double splat operator:

class Child < Parent
def foo(*args, named1: "child", named2: "child", **keyword_args)
puts "Passing to parent: #{[*args, named1: named1, **keyword_args].inspect}"
super(*args, named1: named1, **keyword_args)
end
end

In fact, this works fine when you pass keyword arguments in addition to the positional one:

Child.new.foo({ this: 23 }, named2: "caller")

Prints:

Passing to parent: [{:this=>23}, {:named1=>"child"}]
{:this=>23}
"child"
"parent"

However, since Ruby can't tell the difference between positional arguments and keyword arguments when you only pass a single hash, Child.new.foo({ this: 23 }) results in this: 23 getting interpreted as a keyword argument by the child, and the parent method ends up interpreting both keyword arguments forwarded to it as a single positional argument (a hash) instead:

Child.new.foo({this: 23})

Prints:

Passing to parent: [{:named1=>"child", :this=>23}]
{:named1=>"child", :this=>23}
"parent"
"parent"

There are a few ways you can fix this but none of them are exactly ideal.

Solution 1

As you tried to do in your third example, you could tell the child that the first argument passed will always be a positional argument, and that the rest will be keyword args:

class Child < Parent
def foo(arg, named1: "child", named2: "child", **keyword_args)
puts "Passing to parent: #{[arg, named1: named1, **keyword_args].inspect}"
super(arg, named1: named1, **keyword_args)
end
end

Child.new.foo({this: 23})
Child.new.foo({this: 23}, named1: "custom")

Prints:

Passing to parent: [{:this=>23}, {:named1=>"child"}]
{:this=>23}
"child"
"parent"
Passing to parent: [{:this=>23}, {:named1=>"custom"}]
{:this=>23}
"custom"
"parent"

Solution 2

Switch entirely to using named arguments. This avoids the problem entirely:

class Parent
def foo(positional:, named1: "parent", named2: "parent")
puts positional.inspect
puts named1.inspect
puts named2.inspect
end
end

class Child < Parent
def foo(named1: "child", named3: "child", **args)
super(**args, named1: named1)
end
end

Child.new.foo(positional: {this: 23})
Child.new.foo(positional: {this: 23}, named2: "custom")

Prints:

{:this=>23}
"child"
"parent"
{:this=>23}
"child"
"custom"

Solution 3

Write some code to figure everything out programmatically.

This solution would likely be pretty complex and would depend a lot on exactly how you want it to work, but the idea is that you would use Module#instance_method, and UnboundMethod#parameters to read the signature of the parent's foo method and pass arguments to it accordingly. Unless you really need to do it this way, I'd recommend using one of the other solutions instead.

How to pass optional named arguments in Ruby 2.0

Probably the best way to go would be:

def bar(name: nil)
name ||= 'unknown'
puts name
end

No named parameters in Ruby?

What's actually happening:

# Assign a value of "meh" to scope, which is OUTSIDE meth and equivalent to
# scope = "meth"
# meth(1, scope)
meth(1, scope = "meh")

# Ruby takes the return value of assignment to scope, which is "meh"
# If you were to run `puts scope` at this point you would get "meh"
meth(1, "meh")

# id = 1, options = "meh", scope = "scope"
puts options

# => "meh"

There is no support* for named parameters (see below for 2.0 update). What you're seeing is just the result of assigning "meh" to scope being passed as the options value in meth. The value of that assignment, of course, is "meh".

There are several ways of doing it:

def meth(id, opts = {})
# Method 1
options = opts[:options] || "options"
scope = opts[:scope] || "scope"

# Method 2
opts = { :options => "options", :scope => "scope" }.merge(opts)

# Method 3, for setting instance variables
opts.each do |key, value|
instance_variable_set "@#{key}", value
# or, if you have setter methods
send "#{key}=", value
end
@options ||= "options"
@scope ||= "scope"
end

# Then you can call it with either of these:
meth 1, :scope => "meh"
meth 1, scope: "meh"

And so on. They're all workarounds, though, for the lack of named parameters.


Edit (February 15, 2013):

* Well, at least until the upcoming Ruby 2.0, which supports keyword arguments! As of this writing it's on release candidate 2, the last before the official release. Although you'll need to know the methods above to work with 1.8.7, 1.9.3, etc., those able to work with newer versions now have the following option:

def meth(id, options: "options", scope: "scope")
puts options
end

meth 1, scope: "meh"
# => "options"

Why does specifing a named parameter change the parameters type

Unlike in other languages (such as Python), Ruby strictly separates positional arguments and keyword arguments. You can't provide a keyword parameter as a positional argument when calling a method and vice-versa.

In your case, you have define a single positional parameter in your method (namely b). In your last two examples, you are passing a Hash to it. Note that the curly braces you generally use to define a hash are optional when calling methods and passing a Hash as its last argument. In older Ruby versions, this was used as a convention to allow/pass an optional list of arguments which works similar to keyword arguments.

As a Hash object is always truthy (remember that only false and nil are falsey in Ruby), you are always using the if branch of your conditional and outputting the information you see there.

With that being said, if you want to accept keyword arguments with your method, you have to define it accordingly, e.g.:

def test_param(b: false)
# ...
end

Note the difference in the parameter definition here. You can learn more about keyword arguments in Ruby from the Ruby language documentation.

How can Ruby functions with named arguments get called with a hash instead?

Just call the method with the hash: foo(hash)

The bigger problem: You didn't use named parameters (or better keyword arguments) but parameters with default values. To use named parameters you must not not use = but :.

def foo(x: 'hi', y:'this is y')
puts x
puts y
end

hash = {x: 'first', y: 'second'}
foo(hash)

Ruby - Mixing named and positional parameters, why does order matter?

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:

  • 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:

  • Unexpect behavior when using keyword arguments
  • Can't pass hash to first positional argument; hash interpreted as keyword arguments
  • Keyword argument oddities
  • Splat with empty keyword args gives unexpected results
  • inconsistent behavior using ** vs hash as method parameter
  • Mixing kwargs with optional parameters changes way method parameters are parsed
  • Procs keyword arguments affect value of previous argument
  • Optional argument treated as kwarg
  • Object converted to Hash unexpectedly under certain method call
  • Default Parameters don't work
  • Some weird behaviour with keyword arguments
  • Keyword arguments are ripped from the middle of hash if argument have default value
  • must not optimize foo(**{}) out
  • non-symbol keyword in double splat, such as **{2 => 3}, raises TypeError or not


Related Topics



Leave a reply



Submit