W jaki sposób Ruby zwraca dwie wartości?

95

Zawsze, gdy zamieniam wartości w tablicy, upewniam się, że zapisałem jedną z wartości w zmiennej referencyjnej. Ale odkryłem, że Ruby może zwracać dwie wartości, a także automatycznie zamieniać dwie wartości. Na przykład,

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

Zastanawiałem się, jak Ruby to robi.

Pete
źródło
9
Technicznie Ruby nie zwraca dwóch wartości. Może zwrócić jedną tablicę, która z kolei zostanie przypisana do dwóch zmiennych.
Charles Caldwell,

Odpowiedzi:

166

W przeciwieństwie do innych języków, wartością zwracaną przez każde wywołanie metody w Rubim jest zawsze obiekt. Jest to możliwe, ponieważ, jak wszystko w Rubim, nilsam w sobie jest obiektem.

Zobaczysz trzy podstawowe wzory. Nie zwraca żadnej określonej wartości:

def nothing
end

nothing
# => nil

Zwracanie wartości pojedynczej:

def single
  1
end

x = single
# => 1

Jest to zgodne z tym, czego można oczekiwać od innych języków programowania.

Sytuacja wygląda nieco inaczej, gdy mamy do czynienia z wieloma wartościami zwracanymi. Należy je wyraźnie określić:

def multiple
  return 1, 2
end

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

Podczas wykonywania wywołania, które zwraca wiele wartości, możesz podzielić je na zmienne niezależne:

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

Ta strategia działa również w przypadku tego rodzaju zamian, o których mówisz:

a, b = 1, 2
# => [1, 2]
a, b = b, a
# => [2, 1]
a
# => 2
b
# => 1
tadman
źródło
8
Możesz jawnie zwrócić tablicę [1, 2]i będzie działać tak samo, jak w powyższych przykładach.
Hauleth
6
@hauleth Dobra obserwacja. Powinienem był wyjaśnić, że 1,2sam w sobie jest nieważny, ale albo działa, return 1,2albo [1,2]działa.
tadman
51

Nie, Ruby w rzeczywistości nie obsługuje zwracania dwóch obiektów. (BTW: zwracasz obiekty, a nie zmienne. Dokładniej, zwracasz wskaźniki do obiektów).

Obsługuje jednak przypisywanie równoległe. Jeśli masz więcej niż jeden obiekt po prawej stronie przydziału, obiekty są gromadzone w Array:

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

Jeśli masz więcej niż jeden „cel” (zmienna lub metoda ustawiająca) po lewej stronie przypisania, zmienne zostaną powiązane z elementami Arraypo prawej stronie:

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

Jeśli prawa strona nie jest ikoną Array, zostanie przekonwertowana na jedną przy użyciu to_arymetody

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]

A jeśli połączymy te dwa elementy, otrzymamy to

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

Związany z tym jest operator splat po lewej stronie przydziału. To znaczy „weź wszystkie lewe elementy z Arrayprawej strony”:

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

I wreszcie, równoległe przypisania można zagnieżdżać za pomocą nawiasów:

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]

Kiedy ci returnod sposobu lub nextlub breakz bloku, Ruby będzie traktować tego rodzaju, jakby z prawej strony cesji, więc

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

Nawiasem mówiąc, działa to również w przypadku list parametrów metod i bloków (metody są bardziej rygorystyczne, a bloki mniej rygorystyczne):

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

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

Na przykład bloki są „mniej rygorystyczne” Hash#each. To faktycznie yieldsa pojedyncze dwuelementowa Arrayklucza i wartości do bloku, ale zazwyczaj zapisu

some_hash.each {|k, v| }

zamiast

some_hash.each {|(k, v)| }
Jörg W Mittag
źródło
17

tadman i Jörg W Mittag znają Ruby lepiej niż ja, a ich odpowiedzi nie są błędne, ale nie sądzę, aby odpowiadali na to, co OP chciał wiedzieć. Myślę, że pytanie nie było jednak jasne. W moim rozumieniu to, o co chciał zapytać OP, nie ma nic wspólnego z zwracaniem wielu wartości.


Prawdziwe pytanie brzmi, kiedy chcesz zamienić wartości dwóch zmiennych ai b(lub dwie pozycje w tablicy, jak w oryginalnym pytaniu), dlaczego nie jest konieczne użycie zmiennej czasowej, temptakiej jak:

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

ale można to zrobić bezpośrednio, jak:

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

Odpowiedź jest taka, że ​​w przypadku przypisania wielokrotnego cała prawa strona jest oceniana przed przypisaniem całej lewej strony i nie jest wykonywana pojedynczo. Więc a, b = b, anie jest równoważne a = b; b = a.

Pierwsza ocena całej prawej strony przed przypisaniem jest koniecznością wynikającą z dostosowania, gdy obie strony =mają różną liczbę wyrazów, a opis Jörga W Mittaga może być z tym pośrednio powiązany, ale to nie jest główna kwestia.

sawa
źródło
8

Tablice są dobrym rozwiązaniem, jeśli masz tylko kilka wartości. Jeśli chcesz, aby zwracane były wiele wartości bez znajomości (i dezorientacji) kolejności wyników, alternatywą byłoby zwrócenie skrótu zawierającego dowolne nazwane wartości.

na przykład

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
źródło