我对用于定义块局部变量的好样式感到困惑。选择是:
选择A:
method_that_calls_block { |v, w| puts v, w }
选择B:
method_that_calls_block { |v; w| puts v, w }
当我希望块local具有默认值时,会产生混淆。我困惑的选择是:
选择C:
method_that_calls_block { |v, w = 1| puts v, w }
选择D:
method_that_calls_block { |v, w: 1| puts v, w }
是否有关于如何定义块局部变量的约定?
P.S。当我需要将默认值赋给块局部变量时,似乎;
语法也不起作用!奇怪。
答案 0 :(得分:2)
选择B无效。正如@matt指出的那样 - 它是一种有效的(虽然模糊不清)语法(见这里:How to write an inline block to contain local variable scope in Ruby?)
选择C给出w
的默认值,这是一个常规值,而选择D是默认keyword argument的语法。
答案 1 :(得分:1)
所有这四个都是有效的,但它们都有不同的语义——哪个是正确的,取决于你要完成什么。
考虑以下产生多个值的方法。
def frob
yield 1, 2, 3
end
“给我前两个产生的值,如果有的话,我不关心其他的。”
frob { |v, w| [v, w].inspect}
# => "[1, 2]"
“给我第一个值,我不关心其他的;给我一个额外的,未初始化的变量”。
frob { |v; w| [v, w].inspect}
# => "[1, nil]"
“获取前两个值,如果第二个值未初始化,则将该变量设置为 1”:
frob { |v, w = 1| [v, w].inspect }
# => "[1, 2]" <-- all values are present, default value ignored
“获取前五个值,如果第五个值未初始化,则将该变量设置为 99”:
frob { |v, w, x, y, z = 99| [v, w, x, y, z].inspect }
# => "[1, 2, 3, nil, 99]"
“获取第一个值,如果该方法产生关键字参数 w
,也获取它;如果没有,将其设置为 1。”
frob { |v, w: 1| [v, w].inspect }
# => "[1, 1]"
这是为方法确实产生块参数的情况设计的:
def frobble
yield 1, 2, 3, w: 4
end
frobble { |v, w: 1| [v, w].inspect }
# => "[1, 4]"
在 Ruby < 2.7 中,带有关键字参数的块也将解构散列,尽管 Ruby 2.7 会给您一个 deprecation warning,就像您将散列传递给采用关键字参数的方法一样:
def frobnitz
h = {w: 99}
yield 1, 2, 3, h
end
# Ruby 2.7
frobnitz { |v, w: 1| [v, w].inspect }
# warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
# => "[1, 99]"
Ruby 3.0 不会给你一个弃用警告,但它也会忽略哈希:
# Ruby 3.0
frobnitz { |v, w: 1| [v, w].inspect }
# => [1, 1]
尽管在 3.0 中产生显式关键字参数仍然可以正常工作:
# Ruby 3.0
frobble { |v, w: 1| [v, w].inspect }
# => "[1, 4]"
请注意,如果该方法产生意外的关键字,则关键字参数形式将失败:
def frobnicate
yield 1, 2, 3, w: 99, z: -99
end
frobnicate { |v, w: 1| [v, w].inspect }
# => ArgumentError (unknown keyword: :z)
差异变得明显的另一种方式是在考虑返回数组的方法时:
def gork
yield [1, 2, 3]
end
传递一个带有单个参数的块将获得整个数组:
gork { |v| v.inspect }
# => "[1, 2, 3]"
不过,传递一个带有多个参数的块会得到数组的元素,即使你传递的参数太少或太多:
gork { |v, w| [v, w].inspect }
# "[1, 2]"
gork { |v, w, x, y| [v, w, x, y].inspect }
# => "[1, 2, 3, nil]"
块局部变量的 ;
语法在这里再次派上用场:
gork { |v; w| [v, w].inspect }
# => "[[1, 2, 3], nil]"
但是请注意,即使是关键字参数仍然会导致数组被解构:
gork { |v, w: 99| [v, w].inspect }
# => "[1, 99]"
gork { |v, w: 99; x| [v, w, x].inspect }
# => "[1, 99, nil]"
通常,如果您在块内使用外部变量的名称,则使用的是该变量:
w = 1; frob { |v| w = 99}; w
# => 99
您可以通过上述任何选择来避免这种情况;它们中的任何一个都会隐藏外部变量,从块中隐藏外部变量并确保块对其产生的任何影响都是局部的。
选项 A:块参数:
w = 1; frob { |v, w| puts [v, w].inspect; w = 99}; w
# [1, 2]
# => 1
选择B:块参数+块局部变量
w = 1; frob { |v; w| puts [v, w].inspect; w = 99}; w
# [1, nil]
# => 1
C 选项:块参数,一些有默认值
w = 1; frob { |v, w = 33| puts [v, w].inspect; w = 99}; w
# [1, 2]
# => 1
选择 D:位置和关键字块参数
w = 1; frob { |v, w: 33| puts [v, w].inspect; w = 99}; w
# [1, 33]
# => 1
不过,其他行为差异仍然存在。
不能为块局部变量设置默认值。
frob { |v; w = 1| [v, w].inspect }
# syntax error, unexpected '=', expecting '|'
您也不能将关键字参数用作块参数。
frob { |v; w: 1| [v, w].inspect }
# syntax error, unexpected ':', expecting '|'
如果您知道您正在调用的方法不会产生块参数,那么您可以声明一个带有默认值的假块参数,并使用它来获得预初始化块局部变量。重复上面的第一个选项 D 示例:
frob { |v, w: 1| [v, w].inspect }
# => "[1, 1]"