Optional argument after splat argument

2019-01-03 11:21发布

问题:

Here is my program:

def calculate(*numbers, options = {})
  add(numbers)      if options[:add]
  subtract(numbers) if options[:add] == false
end

def add(*numbers)
  numbers.reduce(:+)
end

def subtract(*numbers)
  numbers.reduce(:-)
end

p calculate(1,2)

On line 1, it is complaining

tests.rb:1: syntax error, unexpected '=', expecting ')'
def calculate(*numbers, options = {})
________________________________________________^
[Finished in 0.1s with exit code 1]

I thought it might have been a problem with default values in Ruby, because before v1.9, you were required to have all default values in order - but this shouldn't be the issue because my version is

ruby 2.0.0p195 (2013-05-14) [i386-mingw32]

I've tried transposing the spaces all over, because ruby seems to be particular with those things when it comes to methods, but no dice.

Could it be my splat variable *numbers ?

回答1:

You cannot have optional parameters after a splat.

The splat means "use up all of the remaining arguments" but then you provide an optional argument, so how could the interpreter know if the last argument is part of the "numbers" splat or the optional "options"?



回答2:

Thanks @maerics and @JorgWMittag -

When you have a splat, it reserves all arguments, which is why it was not liking my second "options" argument. I fixed this issue by changing my arguments around to -

def calculate(*arguments)
  options = arguments[-1].is_a?(Hash) ? arguments.pop : {}
  options[:add] = true if options.empty?
  return add(*arguments) if options[:add]
  return subtract(*arguments) if options[:subtract]
end


回答3:

You can only have mandatory arguments after the splat argument. Optional arguments must come before the splat.

A pseudo-regex for parameter lists in Ruby is something like this:

mand* opt* splat? mand* (mand_kw | opt_kw)* kwsplat? block?

Here's an example:

def foo(m1, m2, o1=:o1, o2=:o2, *splat, m3, m4, 
          ok1: :ok1, mk1:, mk2:, ok2: :ok2, **ksplat, &blk)
  Hash[local_variables.map {|var| [var, eval(var.to_s)] }]
end

method(:foo).arity
# => -5

method(:foo).parameters
# => [[:req, :m1], [:req, :m2], [:opt, :o1], [:opt, :o2], [:rest, :splat], 
#     [:req, :m3], [:req, :m4], [:keyreq, :mk1], [:keyreq, :mk2], 
#     [:key, :ok1], [:key, :ok2], [:keyrest, :ksplat], [:block, :blk]]

foo(1, 2, 3, 4)
# ArgumentError: missing keywords: mk1, mk2

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

foo(1, 2, 3, 4, mk1: 5, mk2: 6)
# => { m1: 1, m2: 2, o1: :o1, o2: :o2, splat: [], m3: 3, m4: 4, 
#      ok1: :ok1, mk1: 5, mk2: 6, ok2: :ok2, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, mk1: 6, mk2: 7)
# => { m1: 1, m2: 2, o1: 3, o2: :o2, splat: [], m3: 4, m4: 5, 
#      ok1: :ok1, mk1: 6, mk2: 7, ok2: :ok2, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, mk1: 7, mk2: 8)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [], m3: 5, m4: 6, 
#      ok1: :ok1, mk1: 7, mk2: 8, ok2: :ok2, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, mk1: 8, mk2: 9)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5], m3: 6, m4: 7, 
#      ok1: :ok1, mk1: 8, mk2: 9, ok2: :ok2, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, mk1: 9, mk2: 10)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
#      ok1: :ok1, mk1: 9, mk2: 10, ok2: :ok2, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
#      ok1: 9, mk1: 10, mk2: 11, ok2: :ok2, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
#      ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
#      ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13, k4: 14)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
#      ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13, k4: 14}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, 
      ok1: 9, ok2: 10, mk1: 11, mk2: 12, k3: 13, k4: 14) do 15 end
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
#      ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13, k4: 14}, 
#      blk: #<Proc:0xdeadbeefc00l42@(irb):15> }


回答4:

def calculate( *numbers, add: true )
  add ? add( *numbers ) : subtract( *numbers )
end

def add *numbers; numbers.reduce( 0, :+) end

def subtract n1, n2; n1 - n2 end

calculate 1, 2 #=> 3
calculate 3, 1, add: false #=> 2


回答5:

Check out how extract_options! works behind Rails.

http://simonecarletti.com/blog/2009/09/inside-ruby-on-rails-extract_options-from-arrays/



回答6:

Actually, from Ruby 2.0 you can accomplish this by using keyword arguments. If you define a method 'calculate' like this:

def calculate(a, *b, **options)
  return a + b.inject(0, :+) if options[:add]
  return a + b.inject(0, :-) if options[:subtract]
  return 0
end

Then you can call that method with this:

calculate(3, 4, -5, 3, -8, add: true)

And you'll get -3 as a result.



标签: