Destructure a Hash in block arguments in Ruby 2.7
Asked Answered
E

3

14

This:

[{a: 1, b: 2}, {a: 3, b: 4}].each do |a:, b:| p a end

Raises the following warning in Ruby 2.7

warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call

I understand that each is passing a hash to the block, and the block now accepts |a:, b:| as named arguments but, is there any way to correctly destructure the hash in this context?

Echikson answered 4/6, 2020 at 7:15 Comment(4)
I don't have an answer, but did find two relevant discussions: 1 2Pontefract
Thanks! On the other hand, those discussions are pre-2.7 deprecations, so I think that they refer to a different issue.Echikson
Check this discuss.rubyonrails.org/t/….Tetryl
It looks like this behavior was not ever explicitly intended behavior and therefore it is not guaranteed to be backwards compatible. Jeremy Evans' comment here – which Matz ends up agreeing with – explains the logic behind it. bugs.ruby-lang.org/issues/14183#note-114Speleology
P
6

I'm uncertain, but I think perhaps the idea is to use pattern matching for hash destructuring? For example:

{a: 1, b: 2}.tap do |args|
  args in {a: a, b: b} # !!!
  p a
end

Currently by default however, this will display a warning (which can be disabled):

Pattern matching is experimental, and the behavior may change in future versions of Ruby!

Pontefract answered 4/6, 2020 at 9:20 Comment(4)
That's really interesting! On the other hand, I didn't have a proper look at pattern matching yet, but I think that this would not be the real purpose of this feature.Echikson
It works though: [{a: 1, b: 2}, {a: 3, b: 4}].each { |args| args in {a: a, b: b}; p a }. But I am just changing one warning for another. :PEchikson
You can disable warnings via: RUBYOPT='-W:no-deprecated -W:no-experimental'. Perhaps you could decide it's OK to ignore experimental warnings, but not deprecations? Not ideal, I know, but I thought it's an interesting suggestion to share.Pontefract
You could also of course go for a more verbose approach if the goal is purely to avoid warnings - i.e. a = args.fetch(:a); b = args.fetch(:b).Pontefract
S
6

In Ruby 3 you can use the rightward assignment operator =>:

{a: 1, b: 2}.tap do |args|
  args => { a:, b: }
  p a
end
Sheugh answered 16/11, 2022 at 21:51 Comment(1)
Just take care, the Hash must have symbol keys, as least as of Ruby 3.3 Pattern matchingSponger
B
1

If you already know that you have two keys in each Hash as per your example, why not this?

[{a: 1, b: 2}, {a: 3, b: 4}].each do |h|
  a, b = h.values
  p a
end
Brahman answered 4/6, 2020 at 9:26 Comment(3)
The danger of this solution is that it depends on the order of the elements in the hash. For instance, [{a: 1, b: 2}, {b: 3, a: 4}] would yield a wrong result.Echikson
Oops! You are correct, I missed this case. But you can assign a = h[:a]. Don't you?Brahman
To specify the order, use: a, b = h.values_at(:a, :b)Sponger

© 2022 - 2024 — McMap. All rights reserved.