Як Рубі повертає два значення?


94

Кожного разу, коли я міняю місцями значення в масиві, я обов’язково зберігаю одне зі значень у посилальній змінній. Але я виявив, що Ruby може повертати два значення, а також автоматично міняти місцями два значення. Наприклад,

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

Мені було цікаво, як це робить Рубі.


9
Технічно Ruby не повертає двох значень. Він може повернути один масив, який, у свою чергу, призначається двом змінним.
Чарльз Колдуелл

Відповіді:


164

На відміну від інших мов, повертане значення будь-якого виклику методу в Ruby завжди є об'єктом. Це можливо, оскільки, як і все в Ruby, nilсаме по собі є об'єктом.

Ви побачите три основні схеми. Не повертаючи особливого значення:

def nothing
end

nothing
# => nil

Повернення одиничного значення:

def single
  1
end

x = single
# => 1

Це відповідає тому, що ви очікуєте від інших мов програмування.

Що стосується кількох повернених значень, ситуація стає дещо іншою. Їх потрібно чітко вказати:

def multiple
  return 1, 2
end

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

Здійснюючи виклик, який повертає кілька значень, ви можете розбити їх на незалежні змінні:

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

Ця стратегія також працює для тих видів заміщення, про які ви говорите:

a, b = 1, 2
# => [1, 2]
a, b = b, a
# => [2, 1]
a
# => 2
b
# => 1

8
Ви можете явно повернути масив, [1, 2]і це буде працювати так само, як ваші приклади вище.
Хаулет

6
@hauleth Гарне спостереження. Я мав би чітко пояснити, що 1,2саме по собі є недійсним, але або, return 1,2або [1,2]працює.
tadman

50

Ні, Ruby насправді не підтримує повернення двох об’єктів. (ДО: ви повертаєте об'єкти, а не змінні. Точніше, ви повертаєте покажчики на об'єкти.)

Однак він підтримує паралельне призначення. Якщо у вас праворуч від призначення більше кількох об’єктів, об’єкти збираються у Array:

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

Якщо у вас є більше однієї "цілі" (змінної або методу встановлення) з лівого боку призначення, змінні прив'язуються до елементів Arrayправого боку:

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

Якщо права частина не є Array, вона буде перетворена в одну за допомогою to_aryметоду

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, b, c = d, e, f
# is the same as
ary = [d, e, f]
a = ary[0]
b = ary[1]
c = ary[2]

З цим пов'язаний оператор splat в лівій частині завдання. Це означає "взяти всі залишені елементи Arrayправоруч":

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

І останнє, але не менш важливе: паралельні призначення можна вкладати за допомогою дужок:

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]

Коли вас returnвід методу або nextабо breakз блоку, Ruby буде ставитися до такого роду-о , як у правій частині присвоювання, так

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

До речі, це також працює в списках параметрів методів і блоків (при цьому методи є більш суворими, а блоки менш суворими):

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

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

Будучи "менш суворими" блоками, наприклад, змушує Hash#eachпрацювати. Це на самому ділі yieldса одного з двох елементів Arrayключа і значення в блок, але ми зазвичай пишемо

some_hash.each {|k, v| }

замість

some_hash.each {|(k, v)| }

16

tadman та Jörg W Mittag знають Рубі краще за мене, і їх відповіді не є помилковими, але я не думаю, що вони відповідають на те, що ОП хотів би знати. Я думаю, що питання не було ясним. На моє розуміння, те, що ОП хотів запитати, не має нічого спільного з поверненням кількох значень.


Справжнє питання полягає в тому, що коли ви хочете переключити значення двох змінних aта b(або двох позицій у масиві, як у вихідному питанні), чому не потрібно використовувати тимчасову змінну, tempтаку як:

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

але це можна зробити безпосередньо, як:

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

Відповідь полягає в тому, що при багаторазовому присвоєнні оцінюється вся права рука перед призначенням цілої лівої сторони, і це не робиться по одному. Так a, b = b, aне є еквівалентом a = b; b = a.

Перша оцінка всієї правої сторони перед призначенням є необхідністю, яка випливає з коригування, коли обидві сторони =мають різну кількість термінів, і опис Йорга В. Міттага може бути опосередковано пов'язаним з цим, але це не головне питання.


8

Масиви - хороший варіант, якщо у вас є лише кілька значень. Якщо ви хочете отримати кілька повернутих значень, не знаючи (і не заплутавшись) порядком результатів, альтернативою було б повернути хеш, який містить усі потрібні вам іменовані значення.

напр

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.