如何证明 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:

  1. Evaluate the expression. Let O be the resulting value.
  2. 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.

  3. 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 Eb.

跳过。

因此,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 块。