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
