Ruby opinions (111)

63 Name: dmpk2k!hinhT6kz2E : 2007-08-08 05:41 ID:Heaven

All right. I've been meaning to post this for a while, so:

Today's Q (and A?)

I don't like how Ruby handles optional arguments. Let's first take a look at a language that does it decently:

>>> def x(a = 1, b = 2):
... return a * b
...
>>> x()
2
>>> x(a = 2)
4
>>> x(b = 3)
3
>>> x(2, 3)
6
>>> x(b = 3, a = 1)
3

So, let's take a look at the above. I can pass any optional parameter, and I can do it in any order. If I pass it in proper order I don't even need to include the names. Makes sense, right? Let's try that with Ruby:

001:0> def x( a = 1, b = 2 )
002:1> a * b
003:1> end
=> nil
004:0> x()
=> 2
005:0> x( a = 2 )
=> 4
006:0> x( b = 3 )
=> 6

Whoa. How does that last one work? 1 * 3 = 3, not 6!

Ruby's "optional" parameters are positional. So what's happening above is that Ruby is creating a new variable at the call site (e.g.: the a in x(a = 2)), assigning an object to it (the 2), and then returning the result of the expression (the same object: 2) which is then used as the argument to x().

Thanks to Ruby foolishly not having borrowed Perl's great idea of lexical scoping with strict mode, I'm sure any number of Ruby neophytes have been left scratching their heads. As an aside I'll just add that all dynamically-typed languages should borrow that idea, but it seems some language communities are too busy feeling smug and superior, and they completely miss an a lot of gems hidden inside Perl's terrifying exterior. If I had to write something big in a dynamically-typed language today, I'd reach for Perl first, and the above is only one of the reasons.

To continue: optional parameters that are only positional are quite anemic. In fact, their only real use is if you need a quick refactor of a method to add some extra functionality without breaking old code -- if the signature doesn't already include an optional parameter on the end, you can just tack one on. Of course, that doesn't scale at all, because it's impossible to give an optional parameter in the 5th position if the 4th is also optional, unless you also pass the 4th as well. This is brain-damaged, so you're up the creek if you want more than one optional parameter, bub.

How do you really do it in Ruby? Apparently like this:

001:0> def x( options = {} )
002:1> a = options[ :a ] || 1
003:1> b = options[ :b ] || 2
004:1> a * b
005:1> end
=> nil
006:0> x()
=> 2
007:0> x( :a => 2 )
=> 4
008:0> x( :b => 3 )
=> 3
009:0> x( 2, 3 )
ArgumentError: wrong number of arguments (2 for 1)
from (irb):9:in `x'
from (irb):9
from :0

Whoops. I guess once you start down that path, you have to use :a and/or :b symbols every time you want to pass an argument; no more positional shortcuts, which Python allows. So now we have a more verbose hash with required unpacking inside the method -- an idea Ruby shouldn't have borrowed from Perl -- and the call site is likewise a bit longer.

Does anyone who has better familiarity with Ruby know a less ugly way to do this? I'd prefer it to have named optional parameters which are also positional, but I'll settle for anything less.

Name: Link:
Leave these fields empty (spam trap):
More options...
Verification: