Как я могу показать, что цикл Ruby `for` фактически реализован с использованием метода` each`?

В книге Eloquent Ruby (стр. 21, первая редакция, шестая печать) автор (Russ Olsen) выступает за использование метода each вместо цикла for, и это находится в линии со всем, что я читал в другом месте.

Однако автор также утверждает, что одна из причин этого - цикл for на самом деле вызывает метод each, так почему бы просто не вырезать среднего человека и использовать each? Поэтому мне было интересно, как это работает.

Для исследования я выполнил поиск в Ruby repo на github, но мне было трудно определить, где/как я мог видеть это в действии.

Чтобы повторить вопрос:

Как я могу показать, что цикл Ruby for фактически реализован с использованием метода each?

Ответ 1

Вы можете показать это, написав класс, который реализует каждый:

# 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

Запустите это, и вы увидите, что сообщение распечатано, а для цикла - цикл три раза.

В качестве альтернативы (и больше удовольствия) вы можете обезглавить встроенный класс 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

И снова вы увидите, что сообщение напечатано.

Ответ 2

Семантика выражения for определена в Спецификация языка Ruby ISO следующим образом:

§11.4.1.2.3 Выражение for

Синтаксис

  • для выражения for для переменной in выражение do-clause end
  • для переменной левая сторона | с несколькими левыми сторонами

Выражение для выражения не должно быть выражением перехода.

Семантика

A для выражения оценивается следующим образом:

  • Вычислить выражение. Пусть O - результирующее значение.
  • Пусть E - первичный-метод-вызов формы primary-expression [здесь нет термина строки] .each do | block-formal-argument-list | block-body end, где значение первичного выражения равно O, block-formal-argument-list - это переменная for-variable, тело-тело - составная инструкция do-clause.

    Оцените E, но пропустите шаг c § 11.2.2.

  • Значение выражения for-выражения является результирующим значением вызова.

Хорошо, так что в основном это означает, что

for for_variable in expression
  do_clause
end

оценивается так же, как

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

Ага! Но мы что-то забыли! Там этот зловещий "пропустите шаг c §11.2.2". вещь! Итак, что делает шаг c § 11.2.2. сказать?

  • Нажмите пустой набор привязок локальных переменных на привязки ⟦local-variable-bindings.

Обратите внимание, что шаг b

  • Задайте контекст выполнения E b.

не пропускается.

Итак, цикл for получает свой собственный контекст выполнения, который начинается как копия текущего контекста выполнения, но он не получает свой собственный набор привязок локальных переменных. IOW: он получает свой собственный динамический контекст выполнения, но не свою лексическую область.

Ответ 3

Как я могу показать, что цикл Ruby for фактически реализован с использованием каждого метода?

Посмотрите на байт-код.

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: [email protected], 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: [email protected], 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            

Как вы можете видеть, он вызывает each с блоком в первой строке 0004.