In Ruby 2.7, I can effectively deconstruct a hash in block parameters:
>> RUBY_VERSION
=> "2.7.6"
>> [{foo: 123}].each { |foo:| p foo }
123
=> [{:foo=>123}]
In Ruby 3.1, I can't:
>> RUBY_VERSION
=> "3.1.2"
>> [{foo: 123}].each { |foo:| p foo }
(irb):7:in `block in <top (required)>': missing keyword: :foo (ArgumentError)
It is possible to pattern match it outside the parameter list:
[{foo: 123}].each { |x| x => {foo:}; p foo }
But I'm after something in the parameter list, if possible.
CodePudding user response:
Short answer: no
each
just passes the {foo: 123}
hash as the block argument, but Ruby 3 expects an actual foo:
keyword to be passed. It's equivalent to:
def m(foo:)
p foo
end
m({foo: 123})
# ArgumentError: wrong number of arguments (given 1, expected 0; required keyword: foo)
You have to either pass keyword arguments or convert the hash via **
:
m(foo: 123)
# 123
m(**{foo: 123})
# 123
In either way, this has to be "fixed" on the caller's side.
In your code, the caller is each
. To get your code working, you could define your own each
variant which does the required conversion:
module Enumerable
def each_keyword
each { |hash| yield **hash }
end
end
Which works as expected for the example data:
[{foo: 123}].each_keyword { |foo:| p foo }
# 123
However, I would just stick to each
and fetch the value from the passed hash:
[{foo: 123}].each { |h| p h[:foo] }
# 123
If you have a hash, you should probably treat is as one.
CodePudding user response:
Difference in keyword argument handling:
# 2.7
>> [{foo: 123}].each {|*args, **kwargs| puts "args=#{args}, kwargs=#{kwargs}" }
args=[], kwargs={:foo=>123}
# 3.1
>> [{foo: 123}].each {|*args, **kwargs| puts "args=#{args}, kwargs=#{kwargs}" }
args=[{:foo=>123}], kwargs={}
# NOTE: no kwargs, no match.
Closest I could get:
>> [{foo: 1},{foo: 2}].each { _1 => foo:; p foo }
1
2
# in the parameter list
>> [{foo: 1},{foo: 2}].each { |i, foo: i[:foo]| p foo }
1
2