我如何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的评估如下:
- 评估表达式 。 设
O
为结果值。设
E
是primary-expressionforms的主要 方法调用 [此处没有行终止符]。 每次执行| block-formal-argument-list | block-body end ,其中primary-expression的值为O
, block-formal-argument-list是for-variable , block-body是do-clause的复合语句 。评估
E
,但跳过§11.2.2的步骤c。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
一个块。