如何证明 Ruby `for` 循环实际上是使用 `each` 方法实现的?
How can I show that the Ruby `for` loop is in fact implemented using the `each` method?
书中EloquentRuby(第21页,第一版,第六次印刷),作者 (Russ Olsen) 提倡使用 each
方法而不是 for
循环,这与我在其他地方读到的所有内容一致。
然而作者还继续说,这样做的一个原因是 for
循环实际上调用了 each
方法,所以为什么不直接去掉中间人并使用 each
?所以我想知道这实际上是如何工作的。
为了调查,我确实搜索了 github 上的 Ruby 存储库,但发现很难准确定位 where/how 我可以看到它的实际效果。
重述问题:
如何显示 Ruby for
循环实际上是使用 each
方法实现的?
您可以通过编写实现每个的 class 来展示它:
# Demo that for calls each
class ThreeOf
def initialize(value)
@value = value
end
def each(&block)
puts "In Each"
block.call(@value)
block.call(@value)
block.call(@value)
end
end
然后创建一个实例并在 for 循环中使用它:
collection = ThreeOf.new(99)
for i in collection
puts i
end
运行 然后您将看到打印出的消息,并且 "loop" 将循环三遍。
或者(更有趣)你可以猴子修补内置数组 class:
class Array
alias_method :orig_each, :each
def each(*args, &block)
puts "Array Each called!"
orig_each(*args, &block)
end
end
puts "For loop with array"
for i in [1,2,3]
puts i
end
您将再次看到打印的消息。
for
表达式的语义在 ISO Ruby Language Specification 中定义如下:
§11.4.1.2.3 The for
expression
Syntax
- for-expression → for for-variable in expression do-clause end
- for-variable → left-hand-side | multiple-left-hand-side
The expression of a for-expression shall not be a jump-expression.
Semantics
A for-expression is evaluated as follows:
- Evaluate the expression. Let
O
be the resulting value.
Let E
be the primary-method-invocation of the form primary-expression [no line-terminator here].each do | block-formal-argument-list | block-body end, where the value of the primary-expression is O
,the block-formal-argument-list is the for-variable, the block-body is the compound-statement of the do-clause.
Evaluate E
, but skip Step c of §11.2.2.
The value of the for-expression is the resulting value of the invocation.
好的,基本上这意味着
for for_variable in expression
do_clause
end
的计算结果与
相同
O = expression
O.each do |for_variable|
do_clause
end
啊哈!但是我们忘了一件事!有这个不祥的 "skip Step c of §11.2.2." 东西!那么,§11.2.2 的步骤 c 是做什么的。说?
- Push an empty set of local variable bindings onto ⟦local-variable-bindings⟧.
注意步骤b
- Set the execution context to
E
b
.
未跳过。
因此,for
循环获取其自己的执行上下文,它以当前执行上下文的副本开始,但它 不会 获取其自己的集合局部变量绑定。 IOW:它有自己的动态执行上下文,但没有自己的词法范围。
How can I show the Ruby for loop is in fact implemented using the each method?
查看字节码。
ruby --dump insns -e 'for n in 1..10; puts n; end'
打印
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
== catch table
| catch type: break st: 0002 ed: 0006 sp: 0000 cont: 0006
|------------------------------------------------------------------------
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] n
0000 trace 1 ( 1)
0002 putobject 1..0
0004 send <callinfo!mid:each, argc:0, block:block in <compiled>>
0006 leave
== disasm: <RubyVM::InstructionSequence:block in <compiled>@<compiled>>=
== catch table
| catch type: redo st: 0006 ed: 0013 sp: 0000 cont: 0006
| catch type: next st: 0006 ed: 0013 sp: 0000 cont: 0013
|------------------------------------------------------------------------
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] ?<Arg>
0000 getlocal_OP__WC__0 2 ( 1)
0002 setlocal_OP__WC__1 2
0004 trace 256
0006 trace 1
0008 putself
0009 getlocal_OP__WC__1 2
0011 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
0013 trace 512
0015 leave
如您所见,它在第一个 0004
行调用 each
块。
书中EloquentRuby(第21页,第一版,第六次印刷),作者 (Russ Olsen) 提倡使用 each
方法而不是 for
循环,这与我在其他地方读到的所有内容一致。
然而作者还继续说,这样做的一个原因是 for
循环实际上调用了 each
方法,所以为什么不直接去掉中间人并使用 each
?所以我想知道这实际上是如何工作的。
为了调查,我确实搜索了 github 上的 Ruby 存储库,但发现很难准确定位 where/how 我可以看到它的实际效果。
重述问题:
如何显示 Ruby for
循环实际上是使用 each
方法实现的?
您可以通过编写实现每个的 class 来展示它:
# Demo that for calls each
class ThreeOf
def initialize(value)
@value = value
end
def each(&block)
puts "In Each"
block.call(@value)
block.call(@value)
block.call(@value)
end
end
然后创建一个实例并在 for 循环中使用它:
collection = ThreeOf.new(99)
for i in collection
puts i
end
运行 然后您将看到打印出的消息,并且 "loop" 将循环三遍。
或者(更有趣)你可以猴子修补内置数组 class:
class Array
alias_method :orig_each, :each
def each(*args, &block)
puts "Array Each called!"
orig_each(*args, &block)
end
end
puts "For loop with array"
for i in [1,2,3]
puts i
end
您将再次看到打印的消息。
for
表达式的语义在 ISO Ruby Language Specification 中定义如下:
§11.4.1.2.3 The
for
expressionSyntax
- for-expression → for for-variable in expression do-clause end
- for-variable → left-hand-side | multiple-left-hand-side
The expression of a for-expression shall not be a jump-expression.
Semantics
A for-expression is evaluated as follows:
- Evaluate the expression. Let
O
be the resulting value.Let
E
be the primary-method-invocation of the form primary-expression [no line-terminator here].each do | block-formal-argument-list | block-body end, where the value of the primary-expression isO
,the block-formal-argument-list is the for-variable, the block-body is the compound-statement of the do-clause.Evaluate
E
, but skip Step c of §11.2.2.The value of the for-expression is the resulting value of the invocation.
好的,基本上这意味着
for for_variable in expression
do_clause
end
的计算结果与
相同O = expression
O.each do |for_variable|
do_clause
end
啊哈!但是我们忘了一件事!有这个不祥的 "skip Step c of §11.2.2." 东西!那么,§11.2.2 的步骤 c 是做什么的。说?
- Push an empty set of local variable bindings onto ⟦local-variable-bindings⟧.
注意步骤b
- Set the execution context to
E
b
.
未跳过。
因此,for
循环获取其自己的执行上下文,它以当前执行上下文的副本开始,但它 不会 获取其自己的集合局部变量绑定。 IOW:它有自己的动态执行上下文,但没有自己的词法范围。
How can I show the Ruby for loop is in fact implemented using the each method?
查看字节码。
ruby --dump insns -e 'for n in 1..10; puts n; end'
打印
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
== catch table
| catch type: break st: 0002 ed: 0006 sp: 0000 cont: 0006
|------------------------------------------------------------------------
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] n
0000 trace 1 ( 1)
0002 putobject 1..0
0004 send <callinfo!mid:each, argc:0, block:block in <compiled>>
0006 leave
== disasm: <RubyVM::InstructionSequence:block in <compiled>@<compiled>>=
== catch table
| catch type: redo st: 0006 ed: 0013 sp: 0000 cont: 0006
| catch type: next st: 0006 ed: 0013 sp: 0000 cont: 0013
|------------------------------------------------------------------------
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] ?<Arg>
0000 getlocal_OP__WC__0 2 ( 1)
0002 setlocal_OP__WC__1 2
0004 trace 256
0006 trace 1
0008 putself
0009 getlocal_OP__WC__1 2
0011 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
0013 trace 512
0015 leave
如您所见,它在第一个 0004
行调用 each
块。