块局部变量的语法

时间:2014-06-05 16:55:18

标签: ruby syntax convention

我对用于定义块局部变量的好样式感到困惑。选择是:

选择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。当我需要将默认值赋给块局部变量时,似乎;语法也不起作用!奇怪。

2 个答案:

答案 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

选择 A:块参数

“给我前两个产生的值,如果有的话,我不关心其他的。”

frob { |v, w| [v, w].inspect}
# => "[1, 2]"

选择B:块参数+块局部变量

“给我第一个值,我不关心其他的;给我一个额外的,未初始化的变量”。

frob { |v; w| [v, w].inspect}
# => "[1, nil]"

C 选项:块参数,一些有默认值

“获取前两个值,如果第二个值未初始化,则将该变量设置为 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]"

选择 D:位置和关键字块参数

“获取第一个值,如果该方法产生关键字参数 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]"