我如何certificateRuby`for`循环实际上是使用`each`方法实现的?

Eloquent Ruby(第21页,第一版,第六版)中 ,作者(Russ Olsen)主张使用each方法而不是for循环,这与我在其他地方读过的所有内容一致。

然而,作者还接着说,这样做的一个原因是for循环实际上调用了each方法,那么为什么不切断中间人并使用each呢? 所以我想知道这实际上是如何工作的。

为了调查我确实搜索了github上的Ruby repo,但发现很难确定我在哪里/如何看到这个。

重申一下这个问题:

如何显示Ruby for循环实际上是使用each方法实现的?

您可以通过编写实现每个的类来显示它:

 # 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 

运行它,你会看到打印出的消息,for“loop”将循环三次。

或者(更有趣)你可以修补内置的Array类:

 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语言规范中定义如下:

§11.4.1.2.3表达式

句法

  • for-expression 表达式 do-clause 结束中的 for-variable
  • for-variable 左侧 | 多左手侧

for表达式的表达式不应是跳转表达式

语义

for-expression的评估如下:

  1. 评估表达式 。 设O为结果值。
  2. Eprimary-expressionforms的主要 方法调用 [此处没有行终止符]。 每次执行| block-formal-argument-list | block-body end ,其中primary-expression的值为Oblock-formal-argument-listfor-variableblock-bodydo-clause复合语句

    评估E ,但跳过§11.2.2的步骤c。

  3. for-expression的值是调用的结果值。

好吧,基本上这意味着

 for for_variable in expression do_clause end 

评价与评价相同

 O = expression O.each do |for_variable| do_clause end 

啊哈! 但我们忘记了什么! 这是一个不祥的“跳过§11.2.2的步骤c”。 事情! 那么,§11.2.2的步骤c是什么? 说?

  • 将一组空的局部变量绑定推送到⟦local-variable-bindings⟧。

注意步骤b

  • 将执行上下文设置为E b

没有被跳过。

因此, for循环获得自己的执行上下文,该上下文作为当前执行上下文的副本开始,但它没有获得自己的一组局部变量绑定。 IOW:它获得了自己的动态执行上下文,但没有自己的词法范围。

如何显示Ruby for循环实际上是使用每种方法实现的?

看一下字节码。

 ruby --dump insns -e 'for n in 1..10; puts n; end' 

哪个打印

 == disasm: @>========== == 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 > 0006 leave == disasm: @>= == 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] ? 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  0013 trace 512 0015 leave 

正如你所看到的那样,它在第一个0004线上each一个块。