Misc #18609
closedkeyword decomposition in enumerable (question/guidance)
Description
There is a pattern that I have used somewhat often in ruby 2, decomposing hash keys as keyword arguments to blocks for Enumerable, which no longer works in ruby 3. I'm wondering if there is a better way that I am missing (I couldn't find any discussion of this particular thing searching this tracker).
drafts = [
{name: 'draft4', mod: :Draft04, image: 'draft4.png'},
{name: 'draft6', mod: :Draft06, image: 'draft6.jpg'},
]
# ruby 2
drafts.each do |name: , mod: , image: |
...
# ruby 3
drafts.each do |draft|
name = draft[:name]
mod = draft[:mod]
image = draft[:image]
...
the latter is much more cumbersome, but seems necessary with the switch in keyword argument handling in ruby 3.
I can refactor to name, mod, image = draft[:name], draft[:mod], draft[:image]
. that is a little better but still more verbose and repetitive than it used to be, and with more keys the line gets very long.
I am expecting this pattern is just a casualty of the keyword split that I will have to rewrite and this issue can be closed, but hoping there may be some better option I have missed.
Updated by jeremyevans0 (Jeremy Evans) over 2 years ago
If you don't want to change too much code, you can define your own method like this if you want to automatically keyword splat:
module Enumerable
def each_kw
each{|v| yield(**v)}
end
end
drafts.each_kw do |name: , mod: , image: |
# ...
end
Updated by sawa (Tsuyoshi Sawada) over 2 years ago
Perhaps, you can also do this:
drafts.each do |draft|
name, mod, image = draft.values_at(:name, :mod, :image)
...
or if you are sure about the order of the keys, even this (although fragile):
drafts.each do |draft|
name, mod, image = draft.values
...
Updated by Hanmac (Hans Mackowiak) over 2 years ago
interesting, it seems to be changed in between "3.1.0" and "3.1.1"
Updated by baweaver (Brandon Weaver) over 2 years ago
sawa (Tsuyoshi Sawada) wrote in #note-2:
Perhaps, you can also do this:
drafts.each do |draft| name, mod, image = draft.values_at(:name, :mod, :image) ...
or if you are sure about the order of the keys, even this (although fragile):
drafts.each do |draft| name, mod, image = draft.values ...
If we want to be really interesting we can take a note from pattern matching on those:
drafts.each do |name:, mod: { id:, creator: }, image: { url: }|
# ...
end
But that's pretty overkill and Whitequark / parser creators would hate us for it.
Anyways, if it's a regression from 3.1.0 to 3.1.1 should probably be patched back, as that's been something I've used myself in the past too.
Updated by Eregon (Benoit Daloze) over 2 years ago
- Status changed from Open to Closed
Hanmac (Hans Mackowiak) wrote in #note-3:
interesting, it seems to be changed in between "3.1.0" and "3.1.1"
This is not true, AFAIK this behavior on purpose since Ruby 3.0.0.
Also please don't claim something like that without a concrete example and result.
Concretely:
$ ruby -ve '[{a: 1}].each { |a:| p a }'
ruby 2.7.5p203 (2021-11-24 revision f69aeb8314) [x86_64-linux]
-e:1: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
-e:1: warning: The called method is defined here
1
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]
-e:1:in `block in <main>': missing keyword: :a (ArgumentError)
ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [x86_64-linux]
-e:1:in `block in <main>': missing keyword: :a (ArgumentError)
ruby 3.1.1p18 (2022-02-18 revision 53f5fc4236) [x86_64-linux]
-e:1:in `block in <main>': missing keyword: :a (ArgumentError)
ruby 3.2.0dev (2022-03-03T08:56:31Z master c1790f8c11) [x86_64-linux]
-e:1:in `block in <main>': missing keyword: :a (ArgumentError)
What @jeremyevans0 (Jeremy Evans) said is the way, let's close.
Updated by Eregon (Benoit Daloze) over 2 years ago
To clarify the example does not pass keyword arguments to a block (same for a method) requiring keyword arguments, that's always going to fail -- by design -- on Ruby 3+.
Updated by Hanmac (Hans Mackowiak) over 2 years ago
Eregon (Benoit Daloze) wrote in #note-5:
Hanmac (Hans Mackowiak) wrote in #note-3:
interesting, it seems to be changed in between "3.1.0" and "3.1.1"
This is not true, AFAIK this behavior on purpose since Ruby 3.0.0.
Also please don't claim something like that without a concrete example and result.
I was testing this on https://try.ruby-lang.org/ because i don't have more ruby versions installed right now, and this did work:
p RUBY_VERSION
drafts = [
{name: 'draft4', mod: :Draft04, image: 'draft4.png'},
{name: 'draft6', mod: :Draft06, image: 'draft6.jpg'},
]
drafts.each do |name: , mod: , image: |
p [name, mod, image]
end
results in:
"3.1.0"
["draft4", "Draft04", "draft4.png"]
["draft6", "Draft06", "draft6.jpg"]
Updated by Eregon (Benoit Daloze) over 2 years ago
https://try.ruby-lang.org/ is Opal by default (clearer on https://try.ruby-lang.org/playground/), and that is not fully compatible with CRuby.
That explains what you saw, thanks for the reply.
=> https://github.com/ruby/TryRuby/issues/122
Updated by Ethan (Ethan -) over 2 years ago
thank you all for the feedback and suggestions, much appreciated.