Como Ruby retorna dois valores?

94

Sempre que troco valores em uma matriz, certifico-me de que armazenei um dos valores em uma variável de referência. Mas descobri que Ruby pode retornar dois valores, bem como trocar dois valores automaticamente. Por exemplo,

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

Eu queria saber como Ruby faz isso.

Pete
fonte
9
Tecnicamente, Ruby não retorna dois valores. Ele pode retornar um array que, por sua vez, é atribuído a duas variáveis.
Charles Caldwell

Respostas:

164

Ao contrário de outras linguagens, o valor de retorno de qualquer chamada de método em Ruby é sempre um objeto. Isso é possível porque, como tudo em Ruby, nilele mesmo é um objeto.

Existem três padrões básicos que você verá. Não retornando nenhum valor particular:

def nothing
end

nothing
# => nil

Retornando um valor singular:

def single
  1
end

x = single
# => 1

Isso está de acordo com o que você esperaria de outras linguagens de programação.

As coisas ficam um pouco diferentes ao lidar com vários valores de retorno. Eles precisam ser especificados explicitamente:

def multiple
  return 1, 2
end

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

Ao fazer uma chamada que retorna vários valores, você pode dividi-los em variáveis ​​independentes:

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

Essa estratégia também funciona para os tipos de substituição de que você está falando:

a, b = 1, 2
# => [1, 2]
a, b = b, a
# => [2, 1]
a
# => 2
b
# => 1
Tadman
fonte
8
Você pode retornar explicitamente um array [1, 2]e isso funcionará da mesma forma que os exemplos acima.
Hauleth
6
@hauleth Uma boa observação. Eu deveria ter deixado claro que 1,2por si só é inválido, mas qualquer um return 1,2ou[1,2] funciona.
tadman
50

Não, Ruby na verdade não suporta o retorno de dois objetos. (BTW: você retorna objetos, não variáveis. Mais precisamente, você retorna ponteiros para objetos.)

No entanto, ele oferece suporte à atribuição paralela. Se você tiver mais de um objeto no lado direito de uma atribuição, os objetos serão coletados em Array:

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

Se você tiver mais de um "destino" (variável ou método setter) no lado esquerdo de uma atribuição, as variáveis ​​serão vinculadas aos elementos de um Arrayno lado direito:

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

Se o lado direito não for um Array, ele será convertido em um usando o 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]

E se colocarmos os dois juntos, obtemos que

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

Relacionado a isso está o operador splat no lado esquerdo de uma atribuição. Significa "pegar todos os elementos restantes Arraydo lado direito":

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

E por último, mas não menos importante, as atribuições paralelas podem ser aninhadas usando parênteses:

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]

Quando você return a partir de um método ou nextou breakde um bloco, Ruby vai tratar este tipo-de como o lado direito de uma atribuição, de modo

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

A propósito, isso também funciona em listas de parâmetros de métodos e blocos (com métodos sendo mais estritos e blocos menos estritos):

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

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

Blocos sendo "menos rígidos" é, por exemplo, o que faz Hash#eachfuncionar. Na verdade, é yieldum único elemento de doisArray de de chave e valor para o bloco, mas geralmente escrevemos

some_hash.each {|k, v| }

ao invés de

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

tadman e Jörg W Mittag conhecem Ruby melhor do que eu, e suas respostas não estão erradas, mas eu não acho que eles estão respondendo o que OP queria saber. Acho que a questão não estava clara. No meu entendimento, o que OP queria perguntar não tem nada a ver com o retorno de vários valores.


A verdadeira questão é, quando você deseja trocar os valores de duas variáveis ae b(ou duas posições em uma matriz como na questão original), por que não é necessário usar uma variável temporal tempcomo:

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

mas pode ser feito diretamente como:

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

A resposta é que na atribuição múltipla, todo o lado direito é avaliado antes da atribuição de todo o lado esquerdo e não é feito um por um. Então a, b = b, anão é equivalente aa = b; b = a .

Avaliar primeiro todo o lado direito antes da atribuição é uma necessidade que decorre do ajuste quando os dois lados =têm diferentes números de termos, e a descrição de Jörg W Mittag pode estar indiretamente relacionada a isso, mas essa não é a questão principal.

sawa
fonte
8

As matrizes são uma boa opção se você tiver apenas alguns valores. Se você quiser vários valores de retorno sem precisar saber (e ser confundido) a ordem dos resultados, uma alternativa seria retornar um Hash que contenha quaisquer valores nomeados que você deseja.

por exemplo

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
fonte