Проблема с блоками Ruby - PullRequest
       42

Проблема с блоками Ruby

2 голосов
/ 23 ноября 2010

Что не так в коде?

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"}

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

Я получаю ошибку: в `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 ]

7 голосов
/ 23 ноября 2010

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

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' }
3 голосов
/ 23 ноября 2010

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

return call_block(n-1) { yield } + call_block(n-2) { yield }
1 голос
/ 23 ноября 2010

Это из-за рекурсивного вызова метода 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"}

РЕДАКТИРОВАТЬ: Я должен признать, что решение , опубликованное правосудием , кажется более логичным.

...