Во-первых, давайте чистить, что немного, так что это легче увидеть, что происходит не так:
def call_block(n)
return 0 if n == 1
return 1 if n == 2
yield
call_block(n-1) + call_block(n-2)
end
puts call_block(10) { puts 'Take this' }
Теперь давайте просто проследить его до конца.
Мы начинаем с вызова
call_block(10) { puts 'Take this' }
Так, n
является 10
и блок {путы «Возьмите это»}. Поскольку n
не является ни 1
, ни 2
, мы приходим к yield
, который передает управление блоку.
Теперь мы называем
call_block(n-1)
который
call_block(9)
Обратите внимание, что мы не называем его с блоком. Итак, для этого нового звонка n
- 9
, и блока нет. Опять же, мы пропустим первые две строки и перейдем к yield
.
Но нет никакого блока до yield
, и именно поэтому код взрывается здесь.
Решение является очевидным и тонким. Очевидная часть: проблема заключается в том, что мы не передаем блок, поэтому нам нужно передать блок. Тонкая часть: как мы это делаем?
То, что делает блоки Ruby настолько синтаксически легкими, заключается в том, что они анонимны. Но если в блоке нет имени, мы не можем ссылаться на него, и если мы не можем ссылаться на него, мы не сможем его передать.
Решение этой проблемы заключается в использовании другой конструкции в Ruby, которая в основном представляет собой более тяжелую абстракцию для идеи «куска кода», чем блок: Proc
.
def call_block(n, blk)
return 0 if n == 1
return 1 if n == 2
blk.()
call_block(n-1, blk) + call_block(n-2, blk)
end
puts call_block(10, ->{ puts 'Take this' })
Как вы можете видеть, это является немного тяжелее синтаксически, но мы можем дать Proc
имя, и, таким образом, передать его вместе с рекурсивных вызовов.
Однако этот шаблон на самом деле достаточно распространен, что для него в Ruby есть специальная поддержка. Если вы помещаете сигил &
перед именем параметра в списке параметров, Ruby будет «упаковывать» блок, который передается в качестве аргумента в объект Proc
и привязывает его к этому имени. И наоборот, если вы поставите &
сигила перед выражением аргумента в списке аргументов, он будет «распаковать», что Proc
в блок:
def call_block(n, &blk)
return 0 if n == 1
return 1 if n == 2
yield # or `blk.()`, whichever you prefer
call_block(n-1, &blk) + call_block(n-2, &blk)
end
puts call_block(10) { puts 'Take this' }
этих «доходность» заявления внутри иначе пустые блоков - которые блоки, которым они уступают? Я смущен, я думал, что эти блоки сами вызывались из-за другого результата yield внутри метода call_block. – Bruce 2011-09-28 22:15:18
`yield` вызывает блок или proc, переданные в слот аргумента блока/proc текущего исполняемого метода. – yfeldblum 2011-09-28 22:57:49