Как я могу показать, что цикл 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
.