Во-первых, давайте немного проясним это, чтобы было легче увидеть, что происходит не так:
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, которая в основном является более тяжелой абстракцией для идеи «порции кода», чем блок: a 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' }