Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby method with parameters in double parenthesis

I have come across the following code in erb library. Note the double (( )):

class MyTest
  def location=((filename, lineno))
    @filename = filename
    @lineno = lineno if lineno
  end
end

The following locatia= method is another version without the (( )) for testing:

class MyTest    
  def locatia=(filename, lineno)
    @filename = filename
    @lineno = lineno if lineno
  end
end

I got this result:

a = MyTest.new
a.location = "foo", 34
a # => #<MyTest:0x2a2e428 @filename="foo", @lineno=34>

b = MyTest.new
b.location = "foo"
b # => #<MyTest:0x2a2e338 @filename="foo">

c = MyTest.new
c.locatia = "foo", 34
c # >> `locatia=': wrong number of arguments (given 1, expected 2) (ArgumentError)

The version with double parenthesis works fine. The one with single fails. It must be specified on some level of the source code. Any clues?

like image 807
Peter Camilleri Avatar asked Dec 02 '22 10:12

Peter Camilleri


2 Answers

Destructuring.

location= accepts one parameter. This parameter is assumed to be an array, and is destructured so that the first element of array goes into filename, and the second into lineno. The outer parentheses are the normal (usually optional) parentheses of a method definition; the inner parentheses indicate the structure of the first (and only) parameter.

Here's another example of destructuring at work:

{ foo: 17, bar: 34 }.each.with_index { |(key, value), index|
  p [key, value, index]
}
# => [:foo, 17, 0]
#    [:bar, 34, 1]

Hash#each generates pairs [key, value]; Enumerator#with_index generates pairs of [value, index]. Apply them both, and you get [[key, value], index] passed to the block. We could just do this:

{ foo: 17, bar: 34 }.each.with_index { |pair, index|
  key = pair[0]
  value = pair[1]
  p [key, value, index]
}

but it is so much simpler with destructuring. We could even write (key, value) = pair (or key, value = pair, as single-rvalue arrays are automatically destructured in multi-lvalue assignment) as another example of destructuring.

like image 165
Amadan Avatar answered Dec 05 '22 00:12

Amadan


It's a little unusual to see this in production code, but what's happening here is list expansion in an argument list:

def location=((filename, lineno))
end

What this means is you call it this way:

x.location = 1,2

Where those get expanded into two separate arguments. A mutator method can only take one argument, but that argument can be a list and you can expand that argument into multiple values.

Normally you see this in iterators like:

{ a: 'b', c: 'd' }.each_with_index.map do |(k,v), i|
  # k, v come in as a pair, i is separate
end

Though even then it's pretty rare.

You can also see it in other cases:

a = [ 1, 2 ]
b = 3

# Without list expansion, just one-to-one assignment
x, y, z = a, b
# x => [ 1, 2 ]
# y => 3
# z => nil

# With list expansion
(x, y), z = a, b
# x => 1
# y => 2
# z => 3
like image 30
tadman Avatar answered Dec 04 '22 22:12

tadman