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?
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With