Problema com blocos de rubi
Pergunta
O que há de errado no código?
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"}
Estou tentando usar o rendimento para imprimir, pegue isso além do décimo número de Fibonacci.
Estou recebendo o erro: em `call_block ': nenhum bloco dado (localjumperror)
Até o código seguinte lança erro:
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"}
Solução
Você pode querer usar esta linha, como Adam Vandenberg dicas:
return call_block(n-1) { yield } + call_block(n-2) { yield }
Outras dicas
Primeiro, vamos limpar um pouco isso para que seja mais fácil ver o que está dando errado:
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' }
Agora vamos apenas rastreá -lo.
Começamos ligando
call_block(10) { puts 'Take this' }
Então, n
é 10
e o bloco é {puts 'Take This'}. Desde n
não é nenhum 1
nem 2
, chegamos ao yield
, que transfere o controle do bloco.
Agora estamos ligando
call_block(n-1)
qual é
call_block(9)
Observe que não estamos chamando isso com um bloco. Então, para esta nova chamada, n
é 9
E não há bloco. Novamente, pulamos as duas primeiras linhas e chegamos ao yield
.
Mas não há bloqueio para yield
Para, e é por isso que o código explode aqui.
A solução é óbvia e sutil. A parte óbvia é: o problema é que não estamos passando um bloco, portanto, a solução é que precisamos passar o bloco. A parte sutil é: como fazemos isso?
O que torna os bloqueios de rubi tão sintaticamente leves é que eles são anônimos. Mas se o bloco não tiver um nome, não podemos nos referir a ele e, se não pudermos nos referir a ele, não podemos transmiti -lo.
A solução para isso é usar outro construto em Ruby, que é basicamente uma abstração mais pesada para a idéia de "um pedaço de código" do que um bloco: um 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' })
Como você pode ver, este é um pouco mais pesado sintaticamente, mas podemos dar o Proc
um nome e, assim, transmiti -lo às chamadas recursivas.
No entanto, esse padrão é realmente comum o suficiente para que haja apoio especial em Ruby para ele. Se você colocar um &
Sigil na frente de um nome de parâmetro em uma lista de parâmetros, Ruby "empacotará" um bloco que é passado como um argumento em um Proc
Objeto e vincule -o a esse nome. E por outro lado, se você colocar um &
sigil diante de uma expressão de argumento em uma lista de argumentos, ele "descompactará" que Proc
em um quarteirão:
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' }
Isso é por causa da chamada recursiva ao método call_block
sem passar em um bloco. Uma maneira de fazer isso seria:
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"}
Editar: devo admitir que a solução Postado por Justice parece mais lógico.