Подтвердить что ты не робот

Как Ruby возвращает два значения?

Всякий раз, когда я меняю значения в массиве, я уверен, что я сохранил одно из значений в ссылочной переменной. Но я обнаружил, что Ruby может возвращать два значения, а также автоматически менять два значения. Например,

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

Мне было интересно, как это делает Ruby.

4b9b3361

Ответ 1

В отличие от других языков, возвращаемое значение любого вызова метода в 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

Ответ 2

Нет, Ruby фактически не поддерживает возврат двух объектов. (BTW: вы возвращаете объекты, а не переменные. Точнее, вы возвращаете указатели на объекты.)

Однако он поддерживает параллельное назначение. Если у вас есть несколько объектов в правой части задания, объекты собираются в 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 из блока или yield в блок, Ruby будет рассматривать этот вид как правую часть задания, поэтому

return 1, 2
next 1, 2
break 1, 2
yield 1, 2
# is the same as
return [1, 2]
next [1, 2]
break [1, 2]
yield [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)| }

Ответ 3

tadman и Jörg W Mittag знают Ruby лучше меня, и их ответы не ошибаются, но я не думаю, что они отвечают на то, что OP хотел узнать. Я думаю, что вопрос не был ясен. По моему мнению, то, что OP хотел спросить, не имеет ничего общего с возвратом нескольких значений.


Реальный вопрос: когда вы хотите переключать значения двух переменных 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.

Сначала оценка всей правой части перед назначением является необходимостью, вытекающей из корректировки, когда обе стороны = имеют разное количество членов, а описание Jörg W Mittag может быть косвенно связано с этим, но это не главная проблема.

Ответ 4

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

например.

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