Optional arguments with default value in Ruby

25,129

Solution 1

You cannot do that (or something similar) in Ruby < 2.0. The best you could do is:

def my_function(h = {})
  h[:c] ||= 500
  # Use h[:a], h[:b], h[:c]
  ...
end

my_function(b: 100)

Solution 2

Arguments are bound to parameters like this:

  1. As long as there are unbound mandatory parameters at the beginning of the parameter list, bind arguments left-to-right
  2. As long as there are unbound mandatory parameters at the end of the parameter list, bind arguments right-to-left
  3. Any leftover arguments are bound to optional parameters left-to-right
  4. Any leftover arguments are collected into an array and bound to the splat argument
  5. A block is wrapped up into a Proc and bound to the block argument
  6. If there are any unbound parameters or leftover arguments, raise an ArgumentError

Here's an example:

def foo(mand1, mand2, opt1=:opt1, opt2=:opt2, *splat, mand3, mand4, &block)
  p local_variables.map {|v| "#{v} = #{eval(v.to_s)}" }
end

foo 1, 2, 3
# ArgumentError: wrong number of arguments (3 for 4+)

foo 1, 2, 3, 4
# mand1 = 1
# mand2 = 2
# opt1 = opt1
# opt2 = opt2
# splat = []
# mand3 = 3
# mand4 = 4
# block = 

foo 1, 2, 3, 4, 5
# mand1 = 1
# mand2 = 2
# opt1 = 3
# opt2 = opt2
# splat = []
# mand3 = 4
# mand4 = 5
# block = 

foo 1, 2, 3, 4, 5, 6
# mand1 = 1
# mand2 = 2
# opt1 = 3
# opt2 = 4
# splat = []
# mand3 = 5
# mand4 = 6
# block = 

foo 1, 2, 3, 4, 5, 6, 7
# mand1 = 1
# mand2 = 2
# opt1 = 3
# opt2 = 4
# splat = [5]
# mand3 = 6
# mand4 = 7
# block = 

foo 1, 2, 3, 4, 5, 6, 7, 8 do end
# mand1 = 1
# mand2 = 2
# opt1 = 3
# opt2 = 4
# splat = [5, 6]
# mand3 = 7
# mand4 = 8
# block = #<Proc:0x007fdc732cb468@(pry):42>

So, as you can see both from step 3 above and from the example, you cannot do this, because optional parameters are bound left-to-right, but you want to specify the middle argument.

Note that this has implications on API design: you should design your parameter lists in such a way that the most "unstable" optional parameters, i.e. the ones that a user most likely wants to supply themselves, are furthest to the left.

Ruby 2.0 now has keyword arguments, which is exactly what you are looking for:

def foo(m1, m2, o1=:o1, o2=:o2, *s, m3, m4, key1: :key1, key2: :key2, **keys, &b)
  puts local_variables.map {|v| "#{v} = #{eval(v.to_s)}" }
end

foo 1, 2, 3
# ArgumentError: wrong number of arguments (3 for 4+)

foo 1, 2, 3, 4
# m1 = 1
# m2 = 2
# o1 = o1
# o2 = o2
# s = []
# m3 = 3
# m4 = 4
# key1 = key1
# key2 = key2
# b = 
# keys = {}

foo 1, 2, 3, 4, 5
# m1 = 1
# m2 = 2
# o1 = 3
# o2 = o2
# s = []
# m3 = 4
# m4 = 5
# key1 = key1
# key2 = key2
# b = 
# keys = {}

foo 1, 2, 3, 4, 5, 6
# m1 = 1
# m2 = 2
# o1 = 3
# o2 = 4
# s = []
# m3 = 5
# m4 = 6
# key1 = key1
# key2 = key2
# b = 
# keys = {}

foo 1, 2, 3, 4, 5, 6, 7
# m1 = 1
# m2 = 2
# o1 = 3
# o2 = 4
# s = [5]
# m3 = 6
# m4 = 7
# key1 = key1
# key2 = key2
# b = 
# keys = {}

foo 1, 2, 3, 4, 5, 6, 7, 8
# m1 = 1
# m2 = 2
# o1 = 3
# o2 = 4
# s = [5, 6]
# m3 = 7
# m4 = 8
# key1 = key1
# key2 = key2
# b = 
# keys = {}

foo 1, 2, 3, 4, 5, 6, 7, 8, key1: 9
# m1 = 1
# m2 = 2
# o1 = 3
# o2 = 4
# s = [5, 6]
# m3 = 7
# m4 = 8
# key1 = 9
# key2 = key2
# b = 
# keys = {}

foo 1, 2, 3, 4, 5, 6, 7, 8, key1: 9, key2: 10
# m1 = 1
# m2 = 2
# o1 = 3
# o2 = 4
# s = [5, 6]
# m3 = 7
# m4 = 8
# key1 = 9
# key2 = 10
# b = 
# keys = {}

foo 1, 2, 3, 4, 5, 6, 7, 8, key1: 9, key2: 10, key3: 11
# m1 = 1
# m2 = 2
# o1 = 3
# o2 = 4
# s = [5, 6]
# m3 = 7
# m4 = 8
# key1 = 9
# key2 = 10
# b = 
# keys = {:key3=>11}

foo 1, 2, 3, 4, 5, 6, 7, 8, key1: 9, key2: 10, key3: 11, key4: 12 do end
# m1 = 1
# m2 = 2
# o1 = 3
# o2 = 4
# s = [5, 6]
# m3 = 7
# m4 = 8
# key1 = 9
# key2 = 10
# b = #<Proc:0x007fdc75135a48@(pry):77>
# keys = {:key3=>11, key4=>12}

Solution 3

So you're trying to implement keyword arguments? This is supposed to be a new feature in Ruby 2.0, but you can try to mimic it in 1.9.x with a hash of arguments instead. Here's a post that discusses how you can accomplish that, which gives the following code sample:

def foo(options = {})
  options = {bar: 'bar'}.merge(options)
  puts "#{options[:bar]} #{options[:buz]}"
end

foo(buz: 'buz') # => 'bar buz'
Share:
25,129
AdamNYC
Author by

AdamNYC

Updated on August 01, 2022

Comments

  • AdamNYC
    AdamNYC almost 2 years

    I would like to create a function that has optional arguments with default values

    def my_function(a = nil, b=nil, c=500)
    
    end
    

    and call the function with the arguments I would like to specify only

    my_function(b=100)
    

    How do I accomplish this in Ruby 1.9.2?

  • PJP
    PJP about 11 years
    Don't create answers that really only contain a link to an answer. WHEN that link breaks, will your answer be useful? Instead, summarize the pertinent part of that answer.
  • Jörg W Mittag
    Jörg W Mittag about 11 years
    -1. This is wrong. You can have as many optional arguments as you want, and they don't have to be last. def foo(mand1, mand2, opt1=:opt1, opt2=:opt2, *splat, mand3, mand4, &block) p local_variables.map {|v| "#{v} = #{eval(v.to_s)}" } end is perfectly fine.
  • sawa
    sawa about 11 years
    @JörgWMittag Right. I was wrong about that. I will remove that part.
  • Wayne Conrad
    Wayne Conrad almost 8 years
    @JörgWMittag I think that some older version of Ruby did require the arguments having defaults to be, if not last, at least after all "ordinary" arguments.
  • Wayne Conrad
    Wayne Conrad almost 8 years
    Ah, yes. It seems that Ruby 1.9 introduced the ability to default defaults before non-default arguments.
  • NM Pennypacker
    NM Pennypacker about 7 years
    The link is broken