2010-11-23 2 views
2

Что не так в коде?Проблема с блоками Ruby

def call_block(n) 

    if n==1 

    return 0 
    elsif n== 2 

    return 1 
    else 
    yield 
    return call_block(n-1) + call_block(n-2) 

    end 

end 


puts call_block(10) {puts "Take this"} 

Я стараюсь использовать доходность для печати. ​​Возьмите это, кроме десятого числа фибоначчи.

Я получаю сообщение об ошибке: в `call_block ': нет блока дал (LocalJumpError)

Даже следующий код бросает ошибка:

def call_block(n) 

    if n==1 
    yield 
    return 0 
    elsif n== 2 
    yield 
    return 1 
    else 
    yield 
    return call_block(n-1) + call_block(n-2) 

    end 

end 


puts call_block(10) {puts "Take this"} 

ответ

3

Вы можете использовать эту линию, так как Adam Vandenberg Рекомендации:

return call_block(n-1) { yield } + call_block(n-2) { yield } 
+0

этих «доходность» заявления внутри иначе пустые блоков - которые блоки, которым они уступают? Я смущен, я думал, что эти блоки сами вызывались из-за другого результата yield внутри метода call_block. – Bruce 2011-09-28 22:15:18

+0

`yield` вызывает блок или proc, переданные в слот аргумента блока/proc текущего исполняемого метода. – yfeldblum 2011-09-28 22:57:49

1

Это из-за рекурсивный вызов метода call_block без прохождения в блоке. Один из способов сделать это будет:

def call_block(n, &blk) 
    if n == 1 
     return 0 
    elsif n == 2 
     return 1 
    else 
     blk.call() 
     return call_block(n-1, &blk) + call_block(n-2, &blk) 
    end 
end 

puts call_block(4) {puts "Take this"} 

EDIT: Я должен признать, что решение posted by Justice кажется более логичным.

7

Во-первых, давайте чистить, что немного, так что это легче увидеть, что происходит не так:

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' } 
Смежные вопросы