Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby methods and ordering of multiple default values

Tags:

ruby

I seem to not be able to do this (which I used to be able to do in Python). Let me explain ..

Suppose I have the following method in Ruby:

def someMethod(arg1=1,arg2=2,arg3=3)
...
...
...
end

Now to call this method I could do

someMethod(2,3,4)
someMethod(2,3)
someMethod(2)

and the arguments are taken by their respective order.. but what if I want to give arg2 at some point in my programming and want the default values for arg1 and arg3?

I tried writing someMethod(arg2=4) but this doesn't seem to work in Ruby 1.9. What it does is it still thinks that arg1 is 4. In python I could at least get away with this, but in ruby I am not sure. Does anyone have any elegant ideas?

like image 244
Jose Avatar asked Mar 03 '10 12:03

Jose


3 Answers

Only elegant solution for now is to use hash as parameters list:

def some_method(options = {})
  defaults = {:arg1 => 1, :arg2 => 2, :arg3 => 3}
  options = defaults.merge(options)
  ...
end

and later in code use implicit hash when calling your method:

some_method()
some_method(:arg1 => 2, :arg2 => 3, :arg3 => 4)
some_method(:arg2 => 4)

In Ruby 1.9 you can use new hash keys syntax to get ir more like in Python:

some_method(arg1: 2, arg2: 3, arg3: 4)

If you want simpler syntax and still be able to use positional parameters, then I would suggest to play with something like this:

def some_method(*args)
  defaults = {:arg1 => 1, :arg2 => 2, :arg3 => 3}
  options = args.last.is_a?(::Hash) ? args.pop : {}
  options = defaults.merge(options)

  arg1 = args[0] || options[:arg1]
  arg2 = args[1] || options[:arg2]
  arg3 = args[2] || options[:arg3]
  ...
end

some_method()
some_method(2)
some_method(3,4,5)
some_method(arg2: 5)
some_method(2, arg3: 10)

If you would like to mimic Ruby arguments number check for method, you can add:

fail "Unknown parameter name(s) " + (options.keys - defaults.keys).join(", ") + "." unless options.length == defaults.length

EDIT:
I updated my answer with Jonas Elfström's comment

like image 136
MBO Avatar answered Oct 03 '22 15:10

MBO


For what you are trying to do the best way would be to use a Hash as only Argument.

def someMethod(args={})
args[:arg1] ||= 1
args[:arg2] ||= 2
args[:arg3] ||= 3
...

This method would then be called like this

someMethod(:arg1 => 2, :arg3 => 5)

all hashvalues not set will be replaced by their default value (e.g. arg2 = 2).

like image 37
Aurril Avatar answered Oct 03 '22 16:10

Aurril


Ruby doesn't have keyword argument passing. (Not yet, anyway, it is one of the things that might make it into Ruby 2.0. However, there are no guarantees, and work on Ruby 2.0 hasn't even started yet, so there's no telling when or even if we will see keyword arguments in Ruby.)

What really happens, is this: you are passing the expression arg2 = 4 as the only argument in the argument list. Ruby is a strict language with pass-by-value, which means that all arguments are fully evaluated before they are passed to the method. In this case, arg2 = 4 is an assignment expression (in Ruby, everything is an expression (i.e. everything returns a value), there are no statements). You are assigning the immediate literal Fixnum value 4 to the local variable arg2. Assignment expressions always return the value of their right-hand side, in this case 4.

So, you are passing the value 4 as the only argument into your method, which means that it gets bound to the first mandatory parameter if there is one (in this case, there isn't), otherwise to the first optional parameter (in this case arg1).

The usual way to deal with situations like this is to use a Hash instead, like this:

some_method({:arg2 => 4})

Because this pattern is used so often, there is special syntactic support: if the last argument to a method is a Hash, you can leave off the curly braces:

some_method(:arg2 => 4)

In Ruby 1.9, there is another syntactical convenience form: because Hashes are so often used with Symbol keys, there is an alternative Hash literal syntax for that:

some_method({arg2: 4})

Combine them both, and you almost have keyword arguments:

some_method(arg2: 4)

(In fact, both of these shortcuts were specifically added to provide an easy migration path towards possible future versions of Ruby with keyword arguments.)

MacRuby actually supports Smalltalk-style multipart message selectors for interoperability with Objective-C. Note, however, that Smalltalk-style multipart message selectors are not keyword arguments, they behave very differently. Also, of course, this extension is specific to MacRuby and not part of the Ruby Specification and not portable to MRI, YARV, JRuby, XRuby, IronRuby, MagLev, Rubinius, tinyrb, RubyGoLightly, BlueRuby, SmallRuby or any other Ruby Implementation.

like image 43
Jörg W Mittag Avatar answered Oct 03 '22 17:10

Jörg W Mittag