How can I show that the Ruby `for` loop is in fact implemented using the `each` method?

前端 未结 3 726
栀梦
栀梦 2021-01-19 00:32

In the book Eloquent Ruby (page 21, first edition, sixth printing), the author (Russ Olsen) advocates using the each method instead of

相关标签:
3条回答
  • 2021-01-19 01:01

    The semantics of the for expression are defined in the ISO Ruby Language Specification like this:

    §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.

    Okay, so basically this means that

    for for_variable in expression
      do_clause
    end
    

    is evaluated the same as

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

    Aha! But we forgot something! There's this ominous "skip Step c of §11.2.2." thing! So, what does Step c of §11.2.2. say?

    • Push an empty set of local variable bindings onto ⟦local-variable-bindings⟧.

    Note that Step b

    • Set the execution context to Eb.

    is not skipped.

    So, a for loop gets its own execution context, which starts out as a copy of the current execution context, but it does not get its own set of local variable bindings. IOW: it gets its own dynamic execution context, but not its own lexical scope.

    0 讨论(0)
  • 2021-01-19 01:01

    How can I show the Ruby for loop is in fact implemented using the each method?

    Look at the bytecode.

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

    Which prints

    == 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            
    

    As you can see it calls each with a block on the first 0004 line.

    0 讨论(0)
  • 2021-01-19 01:04

    You can show it by writing a class that implements 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
    

    And then make an instance and use it in a for loop:

    collection = ThreeOf.new(99)
    
    for i in collection
      puts i
    end
    

    Run that and you will see the message printed out and the for "loop" will go round three times.

    Alternatively (and more fun) you can monkey patch the built in Array 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
    

    And again you will see the message printed.

    0 讨论(0)
提交回复
热议问题