2

I am attempting to write my own solution to a Ruby exercise from Rubymonk where the purpose is to create three methods (add, subtract, and calculate) so when 'calculate' is called you can determine whether or not numbers are added or subtracted based on what is passed in. I am receiving the following error:

main:11: syntax error, unexpected '=', expecting ')' def calculate(*numbers, options={})

Can anyone tell me what the issue is with my code? Thanks for any and all help!

def add(*numbers)
  numbers.inject(0) {|sum, number| sum + number}
end

def subtract(*numbers)
  numbers.inject{|diff, number| diff - number}
end

def calculate(*numbers, options={})
  result = add(numbers) if options.empty?
  result = add(numbers) if options[:add]
  result = subtract(numbers) if options[:subtract]
  result
end
A.D.
  • 4,487
  • 3
  • 38
  • 50
John
  • 443
  • 8
  • 19

4 Answers4

4
def calculate(*numbers, options={})

is not a valid method definition b/c *numbers takes the place a variable number of arguments. You have two options as I see it -

def calculate(options={}, *numbers)

or

def calculate(*args)
   numbers, options = args[0..-2], args[-1] || {}

if you want to keep the same argument order

Alex.Bullard
  • 5,533
  • 2
  • 25
  • 32
  • Thank for the answers guys. One more question if anyone has time. The solution to the problem shows the code for that part as: def calculate(*arguments) options = arguments[-1].is_a?(Hash) ? arguments.pop : {} etc....... and says that you have to check if the last argument to calculate is a Hash, then remove it from the list before calling add or subtract. Why is that the case, why do you have to extract it? – John Oct 31 '13 at 19:01
0

The splat argument *numbers needs to be the last argument. Otherwise, how would Ruby know when to treat the last argument as options or as the last number?

You can use (*numbers, options) (without a default value), but that would require that you always pass an options hash to the method (otherwise your last number will be set as the options variable instead).

Dylan Markow
  • 123,080
  • 26
  • 284
  • 201
  • Thank for the answers guys. One more question if anyone has time. The solution to the problem shows the code for that part as: def calculate(*arguments) options = arguments[-1].is_a?(Hash) ? arguments.pop : {} etc....... and says that you have to check if the last argument to calculate is a Hash, then remove it from the list before calling add or subtract. Why is that the case, why do you have to extract it? – John Oct 31 '13 at 19:03
  • because if you call `add` with a hash as argument, you will try to add integers to a hash, which makes little sense. Notice that the solution is basicly the same as `extract_options!`, without monkey-patching – m_x Oct 31 '13 at 19:29
  • Many thanks Dylan, I was just finally able to make sense of the logic in my head. – John Oct 31 '13 at 20:00
0

Try this way:
def calculate(options={},*numbers)

Using optional arguments after the fully optional argument ( the * notation) do not work since it creates an ambiguity.

Read more at:

http://www.skorks.com/2009/08/method-arguments-in-ruby/

ronnie bermejo
  • 498
  • 4
  • 10
0

You can't use both a splat and a param with a default as last argument, this is too ambiguous for the parser (how to know that the last arg passed is meant to be the options?)

you can work around this in many ways ; one idiom from rails (active support) is :

def calculate(*args)
  options = args.extract_options!
  # ...
end

where extract_options! is a monkey-patch to Array from ActiveSupport defined as follow :

def extract_options!
  last.is_a?(::Hash) ? pop : {}
end

as a side note :

  • an options hash is not really usefull here. you could pass in just a symbol as first argument, maybe.
  • if you use a hash, logic could be simpler :

     def calculate(*args)
       options = args.extract_options!
       method = options.fetch(:method, :add)
       send method, *args
     end
    
  • on add, you don't need inject(0), injectuses the first element of your array as a first "memo" value if you don't provide one

  • you can pass a symbol to inject, which will be the method called on your "memo" value, with "next value" as argument :

     (1..10).inject(:+)
    
     # this is the same as 
     (1..10).inject{ |memo, next| memo + next }
    
     # or, more exactly
     (1..10).inject{ |memo, next| memo.send :+, next }
    
m_x
  • 12,357
  • 7
  • 46
  • 60