Esses dias, estava montando um código para Ruby usando Procs (blocos de código), salvando esses procs em variáveis e depois rodando-os sobre “bindings” diferentes. Talvez tenha ficado um pouco complicado de entender, mas na prática é algo mais ou menos assim:
class UmaClasse def self.callback_qualquer ...#alguns códigos aqui.... @@bloco = proc do break if condicao_qualquer ...#códigos do meu callback end ...#mais algumas coisas end def salvar instance_eval(&@@bloco) puts "Rodei" end endOu seja, eu posso criar um bloco definindo um callbackqualquer, e depois rodar esse bloco no contexto da “instância”, não da classe (o código que eu fiz, na verdade, é bem mais complexo). Mas, surpreeendentemente, esse código não funciona – ele lança um LocalJumpError se a condição do Proc for satisfeita. Por quê?
Para entender isso, é legal saber como os Procs funcionam. Em ruby, tudo retorna valor, então um Proc também retorna valor. Então, quando você faz um “break”, ou “next”, “redo”, você pode passar um valor também para esses comandos. Por exemplo:
array = [1, 2, 3] a = array.each { |x| puts x } #Valor de "a" é [1, 2, 3] a = array.each { break 2 } #Valor de "a" é 2 a = array.inject(0) { |r, v| next v + r } #Uma forma bizarra de fazer a somatóriaSão todos comandos válidos, e isso pode parecer estranho para quem vem de linguagens como Java ou C++ aonde o “break” não recebe parâmetros, e o “next” e o “redo” são usados apenas em “for” e outros laços semelhantes. O que significa um “redo” em um Proc que não itera? e um “next” ou “break”? Veja os exemplos a seguir:
def dez_vezes 10.times do #o redo desvia o fluxo para cá yield #o "next" desvia o fluxo para cá end puts "Rodei todos os comandos." #o "break desvia o fluxo para cá endOu seja, podemos definir mais claramente:
- redo: Desvia o fluxo imediatamente antes do “yield”
- next: Desvia o fluxo para a linha abaixo do “yield”
- break: Desvia o fluxo para a última linha do método
Como exemplo, o que fariam esses códigos?
def ex1 yield 1 puts "Uma vez" yield 2 puts "Outra vez" yield 3 puts "Mais uma vez" return 10 end a = ex1 { |x| break if x == 2 } b = ex1 { |x| redo if x == 3 } c = ex1 { |x| next 15 }O primeiro exemplo: qual o valor de “a”? O que será impresso na tela?
No segundo exemplo: o que será impresso na tela?
No terceiro exemplo: o que será impresso na tela? Qual o valor de “c”?Quando os exemplos acima forem entendidos, é razoavelmente simples entender o por que o primeiro código desse post dá um LocalJumpError – afinal, foi pedido ao Proc um “break”, mas o método aonde ele foi definido está em outro contexto (classe, ao invés da instância), além de outras complicações – não há um lugar claro aonde o “break” deveria desviar. A forma correta é:
class UmaClasse def self.callback_qualquer ...#alguns códigos aqui.... @@bloco = proc do next if condicao_qualquer ...#códigos do meu callback end ...#mais algumas coisas end def salvar instance_eval(&@@bloco) puts "Rodei" end endPode parecer um pouco estranho usar “next” para sair de um “proc”, mas em muitos casos é necessário (especialmente se você possui algum código depois do bloco). Adicionalmente, eu usei para criar os Procs a palavra-chave “proc”. Vale lembrar que ela é equivalente à palavra-chave “lambda”, e não ao “Proc.new” e consequentemente criará um “lambda” e não um “Proc”. Mas isso já é assunto para outra ocasião.