¿Por qué el retorno explícito hace la diferencia en un proceso?
-
07-07-2019 - |
Pregunta
def foo
f = Proc.new { return "return from foo from inside proc" }
f.call # control leaves foo here
return "return from foo"
end
def bar
b = Proc.new { "return from bar from inside proc" }
b.call # control leaves bar here
return "return from bar"
end
puts foo # prints "return from foo from inside proc"
puts bar # prints "return from bar"
Pensé que la palabra clave return
era opcional en Ruby y que siempre está return
si lo solicita o no. Dado eso, me parece sorprendente que foo
y bar
tengan una salida diferente determinada por el hecho de que foo
contiene un return en
Proc f
.
¿Alguien sabe por qué este es el caso?
Solución
Ruby tiene tres construcciones:
- Un bloque no es un objeto y es creado por
{
...}
odo
.. .end
. - Un proc es un objeto
Proc
creado porProc.new
oproc
. - Un lambda es un
Proc
creado porlambda
(oproc
en Ruby 1.8).
Ruby tiene tres palabras clave que regresan de algo:
-
return
termina el método o lambda en el que se encuentra. -
next
termina el bloque, proc o lambda en el que se encuentra. -
break
termina el método que cedió al bloque o invocó el proceso o lambda en el que se encuentra.
En lambdas, return
se comporta como next
, por cualquier razón. next
y break
se nombran de la forma en que se usan porque se usan más comúnmente con métodos como each
, donde la terminación del bloque hará que la iteración reanudar con el elemento siguiente de la colección, y terminar cada
hará que rompa fuera del ciclo.
Si usa
return
dentro de la definición de foo
, regresará de foo
, incluso si está dentro de un bloque o un proceso. Para regresar de un bloque, puede usar la palabra clave next
en su lugar.
def foo
f = Proc.new { next "return from foo from inside proc" }
f.call # control leaves foo here
return "return from foo"
end
puts foo # prints "return from foo"
Otros consejos
Esta es la semántica para Proc
s; No es necesariamente la semántica para todos los bloques. Estoy de acuerdo en que esto es un poco confuso. Está allí para mayor flexibilidad (y quizás en parte porque Ruby no tiene especificaciones, excepto por su implementación).
El comportamiento se define en la implementación Proc
. Lambda
s se comportan de manera diferente, por lo que si desea que su return
s no salga del método de inclusión, utilice lambdas . O bien, omita la palabra clave return
de su Proc
.
Una investigación profunda de los cierres de Rubys está aquí . Es una exposición fantástica.
Entonces:
def foo
f = Proc.new {
p2 = Proc.new { return "inner proc"};
p2.call
return "proc"
}
f.call
return "foo"
end
def foo2
result = Proc.new{"proc"}.call
"foo2 (proc result is: #{result})"
end
def bar
l = lambda { return "lambda" }
result = l.call
return "bar (lambda result is: #{result})"
end
puts foo
# inner proc
puts foo2
# foo (proc result is: proc)
puts bar
# bar (lambda result is: lambda)
Piénselo de esta manera: Proc.new solo cree un bloque de código que sea parte de la función de llamada. proc / lambda crea una función anónima que tiene enlaces especiales. Unos pequeños ejemplos de código ayudarán:
def foo
f = Proc.new { return "return from foo from inside Proc.new" }
f.call # control leaves foo here
return "return from foo"
end
es equivalente a
def foo
begin
return "return from foo from inside begin/end" }
end
return "return from foo"
end
así que está claro que el retorno solo regresará de la función 'foo'
en contraste:
def foo
f = proc { return "return from foo from inside proc" }
f.call # control stasy in foo here
return "return from foo"
end
es equivalente a (ignorando los enlaces ya que no se usa en este ejemplo):
def unonymous_proc
return "return from foo from inside proc"
end
def foo
unonymous_proc()
return "return from foo"
end
Que es tan claro que no regresará de foo y continuará con la siguiente declaración.