简介:在 Ruby 2.7 中运行的奇怪代码
在维护某个 Rails 项目时,我遇到了这个奇怪的代码。
def sample
data = {
a: nil,
b: { x: , y: },
c: { x: },
d: { y: },
}
data.map do |k, x: , y: |
[k, x, y]
end
end
在 Ruby 2.7 中运行这个 sample
方法会得到以下结果:
# Ruby 2.7
sample
#=> [
# [:a, 0, 0],
# [:b, 1, 2],
# [:c, 1, 0],
# [:d, 0, 2],
# ]
这到底是在做什么?
在这个方法中,下面几行为x
和y
设置了默认值。
data.map do |k, x: , y: |
这会导致该方法表现出以下行为:
-
a: nil
,然后x
和y
都默认为 0。 -
b: { x: 1, y: 2 }
对于x
变成1,对于y
变成2(不使用默认值)。 -
c: { x: 1 }
的x
为 1,y
的默认值为 0。 -
d: { y: 2 }
对于x
的默认值为0,对于y
的默认值为2。
就个人而言,我认为“不要编写如此复杂的代码”,但在 Ruby 2.7 之前它工作得很好,所以在那之前它可能一直很好。
遇到的问题:此代码在 Ruby 3.0+ 中不起作用
但是,在 Ruby 2.7 中使用 -W:deprecated
选项运行时,会出现以下警告消息。
warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
warning: The called method is defined here
如果此代码按原样在 Ruby 3.0 中运行,则 x
和 y
都为 0,这与 Ruby 2.7 不同。
# Ruby 3.0だとxもyも全部0になる!
sample
#=> [
# [:a, 0, 0],
# [:b, 0, 0],
# [:c, 0, 0],
# [:d, 0, 0],
# ]
这就是 Ruby 3.0 中引入的“关键字参数分离”的效果。
如果k, x: 0, y: 0
这样的描述不是块参数而是方法的形参,可以通过如下方式在hash中添加**
来避免这个问题。
# 引用元 https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
def foo(k: )
p k
end
h = { k: }
# **を付けるとハッシュオブジェクトをキーワード引数に変換できる
foo(**h) #=> 42
但是,由于这次是块参数,所以不能使用添加**
的策略。
解决方案:重写代码以获得预期的结果,即使使用 Ruby 3.0
因此,我决定重写代码如下。
def sample
data = {
a: nil,
b: { x: , y: },
c: { x: },
d: { y: },
}
- data.map do |k, x: , y: |
+ data.map do |k, h|
+ x, y = { x: , y: }.merge(h || {}).values_at(:x, :y)
[k, x, y]
end
end
我能够得到与 Ruby 2.7 相同的结果。
# Ruby 3.0(修正後のコード)
sample
#=> [
# [:a, 0, 0],
# [:b, 1, 2],
# [:c, 1, 0],
# [:d, 0, 2],
# ]
我认为还有其他方法可以编写x, y = { x: 0, y: 0 }.merge ...
部分,但似乎没有像“只需添加**
”这样的简单解决方案。
特别感谢
关于这件事,我在 ruby-jp Slack 频道上向 Ruby 程序员咨询了各种解决方案。
非常感谢所有咨询过我的 Ruby 程序员!
奖金
这是我写这篇文章的时候为了检查操作而写的测试代码。
当您想自己移动时,请使用它。
require 'minitest/autorun'
class BlockParamsTest < Minitest::Test
def sample
data = {
a: nil,
b: { x: , y: },
c: { x: },
d: { y: },
}
# Ruby 2.7
data.map do |k, x: , y: |
[k, x, y]
end
# Ruby 3.0
# data.map do |k, h|
# x, y = { x: 0, y: 0 }.merge(h || {}).values_at(:x, :y)
# [k, x, y]
# end
end
def test_sample
expected = [
[:a, , ],
[:b, , ],
[:c, , ],
[:d, , ],
]
assert_equal expected, sample
end
end
原创声明:本文系作者授权九品源码发表,未经许可,不得转载;
原文地址:https://www.19jp.com/show-308624531.html