заставить объект вести себя как массив для параллельного присваивания в ruby - PullRequest
5 голосов
/ 20 апреля 2009

Предположим, вы делаете это в Ruby:

ar = [1, 2]
x, y = ar

Тогда x == 1 и y == 2. Есть ли метод, который я могу определить в моих собственных классах, который даст тот же эффект? например,

rb = AllYourCode.new
x, y = rb

Пока что все, что я смог сделать с таким заданием, это сделать x == rb и y = nil. В Python есть такая функция:

>>> class Foo:
...     def __iter__(self):
...             return iter([1,2])
...
>>> x, y = Foo()
>>> x
1
>>> y
2

Ответы [ 3 ]

7 голосов
/ 20 апреля 2009

Да. Определить #to_ary. Это позволит вашему объекту рассматриваться как массив для присваивания.

irb> o = Object.new
=> #<Object:0x3556ec>
irb> def o.to_ary
       [1, 2]
     end
=> nil
irb> x, y = o
=> [1,2]
irb> x
#=> 1
irb> y
#=> 2

Разница между #to_a и #to_ary в том, что #to_a используется, чтобы попытаться преобразовать данный объект для массива, в то время как #to_ary доступен, если мы можем рассматривать данный объект как массив. Это тонкая разница.

2 голосов
/ 20 апреля 2009

Почти:

class AllYourCode
   def to_a
     [1,2]
   end
end

rb = AllYourCode.new
x, y = *rb
p x
p y

Splat попытается вызвать to_ary, а затем попытается вызвать to_a. Я не уверен, почему вы хотите сделать это, хотя это действительно синтаксическая функция, которая использует Array в своей реализации, а не функцию Array.

Другими словами, варианты использования для множественного назначения - это такие вещи, как:

# swap
x, y = y, x

# multiple return values
quot, rem = a.divmod(b)

# etc.
name, age = "Person", 100

Другими словами, большую часть времени объект, от которого назначается (Array), даже не очевиден.

1 голос
/ 20 апреля 2009

Вы не можете переопределить присваивание, потому что это оператор, а не метод. Но если ваш класс AllYourCode наследуется от Array, ваш пример будет работать.

Когда Ruby встречает назначение, он смотрит на правую сторону и, если существует более одного значения, он собирает их в массив. Затем он смотрит на левую сторону. Если там есть одно значение lvalue, ему присваивается массив.

def foo 
  return "a", "b", "c" # three rvalues
end

x = foo # => x == ["a", "b", "c"]

Если существует более одного l-значения (точнее, если он видит запятую), он последовательно присваивает r-значения и отбрасывает дополнительные.

x, y, z = foo # => x == "a", y == "b", z == "c"
x, y = foo    # => x == "a", y == "b"
x, = foo      # => x == "a"

Вы можете выполнять параллельное присваивание, если массив также возвращается, как вы обнаружили.

def bar
  ["a", "b", "c"]
end

x = bar       # => x == ["a", "b", "c"]
x, y, z = bar # => x == "a", y == "b", z == "c"
x, y = bar    # => x == "a", y == "b"
x, = bar      # => x == "a"

Итак, в вашем примере, если rb является массивом или наследуется от массива, x и y будут назначены его первые 2 значения.

...