¿Cómo devuelve Ruby dos valores?

94

Siempre que cambio valores en una matriz, me aseguro de almacenar uno de los valores en una variable de referencia. Pero descubrí que Ruby puede devolver dos valores, así como intercambiar automáticamente dos valores. Por ejemplo,

array = [1, 3, 5 , 6 ,7]
array[0], array[1] = array[1] , array[0] #=> [3, 1] 

Me preguntaba cómo hace esto Ruby.

Pete
fuente
9
Técnicamente, Ruby no devuelve dos valores. Puede devolver una matriz que a su vez se asigna a dos variables.
Charles Caldwell

Respuestas:

164

A diferencia de otros lenguajes, el valor de retorno de cualquier llamada a método en Ruby es siempre un objeto. Esto es posible porque, como todo en Ruby, en nilsí mismo es un objeto.

Hay tres patrones básicos que verá. Devolviendo ningún valor en particular:

def nothing
end

nothing
# => nil

Devolviendo un valor singular:

def single
  1
end

x = single
# => 1

Esto está en línea con lo que esperaría de otros lenguajes de programación.

Las cosas se ponen un poco diferentes cuando se trata de múltiples valores de retorno. Estos deben especificarse explícitamente:

def multiple
  return 1, 2
end

x = multiple
# => [ 1, 2 ]
x
# => [ 1, 2 ]

Al realizar una llamada que devuelve varios valores, puede dividirlos en variables independientes:

x, y = multiple
# => [ 1, 2 ]
x
# => 1
y
# => 2

Esta estrategia también funciona para los tipos de sustitución de los que está hablando:

a, b = 1, 2
# => [1, 2]
a, b = b, a
# => [2, 1]
a
# => 2
b
# => 1
tadman
fuente
8
Puede devolver explícitamente una matriz [1, 2]y esto funcionará igual que los ejemplos anteriores.
Hauleth
6
@hauleth Una buena observación. Debería haber dejado en claro que 1,2por sí solo no es válido, pero de return 1,2o [1,2]funciona.
tadman
50

No, Ruby no admite la devolución de dos objetos. (Por cierto: devuelve objetos, no variables. Más precisamente, devuelve punteros a objetos).

Sin embargo, admite la asignación en paralelo. Si tiene más de un objeto en el lado derecho de una tarea, los objetos se recopilan en un Array:

foo = 1, 2, 3
# is the same as
foo = [1, 2, 3]

Si tiene más de un "objetivo" (variable o método de establecimiento) en el lado izquierdo de una asignación, las variables se unen a elementos de un Arrayen el lado derecho:

a, b, c = ary
# is the same as
a = ary[0]
b = ary[1]
c = ary[2]

Si el lado derecho no es un Array, se convertirá en uno usando el to_arymétodo

a, b, c = not_an_ary
# is the same as
ary = not_an_ary.to_ary
a = ary[0]
b = ary[1]
c = ary[2]

Y si juntamos los dos, obtenemos eso

a, b, c = d, e, f
# is the same as
ary = [d, e, f]
a = ary[0]
b = ary[1]
c = ary[2]

Relacionado con esto está el operador de símbolos en el lado izquierdo de una asignación. Significa "tomar todos los elementos sobrantes del Arraylado derecho":

a, b, *c = ary
# is the same as
a = ary[0]
b = ary[1]
c = ary.drop(2) # i.e. the rest of the Array

Y por último, pero no menos importante, las asignaciones paralelas se pueden anidar usando paréntesis:

a, (b, c), d = ary
# is the same as
a = ary[0]
b, c = ary[1]
d = ary[2]
# which is the same as
a = ary[0]
b = ary[1][0]
c = ary[1][1]
d = ary[2]

Cuando returna partir de un método o nexto breakde un bloque, Ruby va a tratar este tipo de como el lado derecho de una asignación, por lo

return 1, 2
next 1, 2
break 1, 2
# is the same as
return [1, 2]
next [1, 2]
break [1, 2]

Por cierto, esto también funciona en listas de parámetros de métodos y bloques (siendo los métodos más estrictos y los bloques menos estrictos):

def foo(a, (b, c), d) p a, b, c, d end

bar {|a, (b, c), d| p a, b, c, d }

Los bloques que son "menos estrictos" es, por ejemplo, lo que hace que Hash#eachfuncione. En realidad, yieldes un solo elemento Arrayde dos claves y valor para el bloque, pero generalmente escribimos

some_hash.each {|k, v| }

en vez de

some_hash.each {|(k, v)| }
Jörg W Mittag
fuente
16

tadman y Jörg W Mittag conocen a Ruby mejor que yo, y sus respuestas no son incorrectas, pero no creo que estén respondiendo lo que OP quería saber. Sin embargo, creo que la pregunta no estaba clara. Según tengo entendido, lo que OP quería preguntar no tiene nada que ver con devolver múltiples valores.


La pregunta real es, cuando desea cambiar los valores de dos variables ay b(o dos posiciones en una matriz como en la pregunta original), ¿por qué no es necesario usar una variable temporal tempcomo:

a, b = :foo, :bar
temp = a
a = b
b = temp

pero se puede hacer directamente como:

a, b = :foo, :bar
a, b = b, a

La respuesta es que en la asignación múltiple, todo el lado derecho se evalúa antes de asignar todo el lado izquierdo, y no se hace uno por uno. Entonces a, b = b, ano es equivalente a a = b; b = a.

Primero, evaluar todo el lado derecho antes de la asignación es una necesidad que se deriva del ajuste cuando ambos lados =tienen diferentes números de términos, y la descripción de Jörg W Mittag puede estar indirectamente relacionada con eso, pero ese no es el problema principal.

sawa
fuente
8

Las matrices son una buena opción si solo tiene unos pocos valores. Si desea múltiples valores de retorno sin tener que saber (y confundirse por) el orden de los resultados, una alternativa sería devolver un Hash que contenga los valores con nombre que desee.

p.ej

def make_hash
  x = 1
  y = 2
  {x: x, y: y}
end

hash = make_hash
# => {:x=>1, :y=>2}
hash[:x]
# => 1
hash[:y]
# => 2
pronoob
fuente