57

I don't understand completely how named parameters in Ruby 2.0 work.

def test(var1, var2, var3)
  puts "#{var1} #{var2} #{var3}"
end

test(var3:"var3-new", var1: 1111, var2: 2222) #wrong number of arguments (1 for 3) (ArgumentError)

it's treated like a hash. And it's very funny because to use named parameters in Ruby 2.0 I must set default values for them:

def test(var1: "var1", var2: "var2", var3: "var3")
  puts "#{var1} #{var2} #{var3}"
end

test(var3:"var3-new", var1: 1111, var2: 2222) # ok => 1111 2222 var3-new

which very similar to the behaviour which Ruby had before with default parameters' values:

def test(var1="var1", var2="var2", var3="var3")
  puts "#{var1} #{var2} #{var3}"
end

test(var3:"var3-new", var1: 1111, var2: 2222) # ok but ... {:var3=>"var3-new", :var1=>1111, :var2=>2222} var2 var3

I know why is that happening and almost how it works.

But I'm just curious, must I use default values for parameters if I use named parameters?

And, can anybody tell me what's the difference between these two then?

def test1(var1="default value123")
  #.......
end

def test1(var1:"default value123")
  #.......
end
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Alan Coromano
  • 24,958
  • 53
  • 135
  • 205

9 Answers9

85

I think that the answer to your updated question can be explained with explicit examples. In the example below you have optional parameters in an explicit order:

def show_name_and_address(name="Someone", address="Somewhere")
  puts "#{name}, #{address}"
end

show_name_and_address
#=> 'Someone, Somewhere'

show_name_and_address('Andy')
#=> 'Andy, Somewhere'

The named parameter approach is different. It still allows you to provide defaults but it allows the caller to determine which, if any, of the parameters to provide:

def show_name_and_address(name: "Someone", address: "Somewhere")
  puts "#{name}, #{address}"
end

show_name_and_address
#=> 'Someone, Somewhere'

show_name_and_address(name: 'Andy')
#=> 'Andy, Somewhere'

show_name_and_address(address: 'USA')
#=> 'Someone, USA'

While it's true that the two approaches are similar when provided with no parameters, they differ when the user provides parameters to the method. With named parameters the caller can specify which parameter is being provided. Specifically, the last example (providing only the address) is not quite achievable in the first example; you can get similar results ONLY by supplying BOTH parameters to the method. This makes the named parameters approach much more flexible.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
AndyV
  • 3,696
  • 1
  • 19
  • 17
  • 1
    I would just add for clarity that if you call the first way (with default but not _named_ params) as if it's named params, you don't get an error. Instead ruby interprets it as a single _hash_ argument passed in (to the first arg) – Hari Honor Jan 15 '20 at 12:52
  • @HariKaramSingh maybe you could explain a bit more but I believe that you're off base. In the first approach, Ruby uses _positional_ matching: whatever object is passed in as the first param to the call is called 'name' locally to the method and whatever is passed in second is called 'address'. If you attempt to pass a hash (e.g., show_name_and_address(address: 'Somewhere', name: 'Someone")) the result will be that 'name' will receive the entire has and 'address' will have the default value. – AndyV Jan 15 '20 at 17:49
  • 1
    @AndyV I think think we're saying the same thing. Another way of saying it might be that in ruby, a function call with a hash literal can look exactly the same as a function call with named parameters - the only difference is in the function definition - which can be a bit confusing for noobs. – Hari Honor Jan 17 '20 at 10:28
  • Thanks for your comment @Hari. I spent too long on this today and discovered the same thing. It's really confusing coming from a Python background where this is normal. I've resorted to supplying every parameter in the optional parameters case. – Nathaniel Ruiz Nov 30 '22 at 01:01
24

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".

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
phoet
  • 18,688
  • 4
  • 46
  • 74
  • +1 to the [brainspec link](http://brainspec.com/blog/2012/10/08/keyword-arguments-ruby-2-0/). It's an easy to understand first-read description. – Leigh McCulloch Dec 29 '13 at 10:35
20

As of Ruby 2.1.0, you no longer have to set default values for named parameters. If you omit the default value for a parameter, the caller will be required to provide it.

def concatenate(val1: 'default', val2:)
  "#{val1} #{val2}"
end

concatenate(val2: 'argument')
#=> "default argument"

concatenate(val1: 'change')
#=> ArgumentError: missing keyword: val2

Given:

def test1(var1="default value123")
  var1
end

def test2(var1:"default value123")
  var1
end

They'll behave the same way when not passed an argument:

test1
#=> "default value123"

test2
#=> "default value123"

But they'll behave much differently when an argument is passed:

test1("something else")
#=> "something else"

test2("something else")
#=> ArgumentError: wrong number of arguments (1 for 0)


test1(var1: "something else")
#=> {:var1=>"something else"}

test2(var1: "something else")
#=> "something else"
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
trliner
  • 459
  • 3
  • 11
15

I agree with you that it's weird to require default values as the price for using named parameters, and evidently the Ruby maintainers agree with us! Ruby 2.1 will drop the default value requirement as of 2.1.0-preview1.

Josh Diehl
  • 2,913
  • 2
  • 31
  • 43
  • 9
    Guess what sucks! If you defined a function that takes a keyword argument, and then someone calls it with a plain argument... you get `ArgumentError: wrong number of arguments (1 for 0)` rather than, oh say `ArgumentError: missing keyword for keyword argument (argument 1 of 1)`. – Ziggy Oct 22 '14 at 19:17
  • 1
    I think a common pattern is to use named arguments when the list of arguments is too long, or has too many optionals. In those cases, you probably want to prevent people from just passing a value as an argument and assuming they know the right order. – Josh Diehl Oct 22 '14 at 20:07
  • I use them to avoid this anti-pattern: `def f(arg1, arg2, flag = true)` I instead do `def f(arg1, arg2, flag: true)` so that the person calling the function MUST know the nature of the flag. – Ziggy Oct 23 '14 at 19:01
  • My comment is mainly that the nature of the error message should be more explicit about what went wrong: you didn't use the wrong number of args, you just failed to name some of them correctly. – Ziggy Oct 23 '14 at 19:02
  • 1
    @Ziggy In the case of keyword args with defaults though, there's really no way to know though whether you used the wrong number of args, or just failed to name them correctly. e.g. with `def func(flag: true)`, `func()` is perfectly valid. `func(1)`, however, contains too many arguments, just as `func(1, flag: false)` does. I suppose the error message could be a little more helpful though by suggesting the possibility of incorrectly specified arguments, or perhaps by printing the method signature. – Ajedi32 Jan 13 '15 at 18:59
12

This is present in all the other answers, but I want to extract this essence.

There are four kinds of parameter:

Required Optional
Positional def PR(a) def PO(a=1)
Keyword def KR(a:) def KO(a:1)

When defining a function, positional arguments are specified before keyword arguments, and required arguments before optional ones.

irb(main):006:0> def argtest(a,b=2,c:,d:4)
irb(main):007:1> p [a,b,c,d]
irb(main):008:1> end
=> :argtest

irb(main):009:0> argtest(1,c: 3)
=> [1, 2, 3, 4]

irb(main):010:0> argtest(1,20,c: 3,d: 40)
=> [1, 20, 3, 40]

EDIT: the required keyword argument (without a default value) is new as of Ruby 2.1.0, as mentioned by others.

Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
Chris Cox
  • 390
  • 3
  • 14
5

Leaving this here because it helped me a lot.

Example

Suppose you have this:

def foo(thing, to_print)
  if to_print
    puts thing
  end
end


# this works
foo("hi", true)
# hi
# => nil

so you try adding the argument names, like so:

foo(thing: "hi", to_print: true)
# foo(thing: "hi", to_print: true)
# ArgumentError: wrong number of arguments (given 1, expected 2)
# from (pry):42:in `foo'

but unfortunately it errors.

Solution

Just add a : to the end of each argument:

def foo2(thing:, to_print:)
  if to_print
    puts thing
  end
end


foo2(thing: "hi", to_print: true)
# hi
# => nil

And it works!

stevec
  • 41,291
  • 27
  • 223
  • 311
3

According to "Ruby 2.0.0 by Example" you must have defaults:

In Ruby 2.0.0, keyword arguments must have defaults, or else must be captured by **extra at the end.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
2
def test(a = 1, b: 2, c: 3)
  p [a,b,c]
end

test #=> [1,2,3]
test 10 #=> [10,2,3]
test c:30 #=> [1,2,30] <- this is where named parameters become handy. 

You can define the default value and the name of the parameter and then call the method the way you would call it if you had hash-based "named" parameters but without the need to define defaults in your method.

You would need this in your method for each "named parameter" if you were using a hash.

b = options_hash[:b] || 2

as in:

  def test(a = 1, options_hash)
    b = options_hash[:b] || 2
    c = options_hash[:c] || 3
    p [a,b,c]
  end
aaandre
  • 2,502
  • 5
  • 33
  • 46
0

You can define named parameters like

def test(var1: var1, var2: var2, var3: var3)
  puts "#{var1} #{var2} #{var3}"
end

If you don't pass one of the parameters, then Ruby will complain about an undefined local variable or method.

Clemens Helm
  • 3,841
  • 1
  • 22
  • 13