make an object behave like an Array for parallel assignment in ruby

痞子三分冷 提交于 2019-12-22 07:25:50

问题


Suppose you do this in Ruby:

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

Then, x == 1 and y == 2. Is there a method I can define in my own classes that will produce the same effect? e.g.

rb = AllYourCode.new
x, y = rb

So far, all I've been able to do with an assignment like this is to make x == rb and y = nil. Python has a feature like this:

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

回答1:


Yep. Define #to_ary. This will let your object be treated as an array for assignment.

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

The difference between #to_a and #to_ary is that #to_a is used to try to convert a given object to an array, while #to_ary is available if we can treat the given object as an array. It's a subtle difference.




回答2:


Almost:

class AllYourCode
   def to_a
     [1,2]
   end
end

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

Splat will try to invoke to_ary, and then try to invoke to_a. I'm not sure why you want to do this though, this is really a syntactical feature that happens to use Array in its implementation, rather than a feature of Array.

In other words the use cases for multiple assignment are things like:

# swap
x, y = y, x

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

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

In other words, most of the time the object being assigned from (the Array) isn't even apparent.




回答3:


You can't redefine assignment, because it's an operator instead of a method. But if your AllYourCode class were to inherit from Array, your example would work.

When Ruby encounters an assignment, it looks at the right hand side and if there is more than one rvalue, it collects them into an array. Then it looks at the left hand side. If there is one lvalue there, it is assigned the array.

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

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

If there is more than one lvalue (more specifically, if it sees a comma), it assigns rvalues successively and discards the extra ones.

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

You can do parallel assignment if an array is returned, too, as you have discovered.

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"

So in your example, if rb is an Array or inherits from Array, x and y will be assigned its first 2 values.



来源:https://stackoverflow.com/questions/766712/make-an-object-behave-like-an-array-for-parallel-assignment-in-ruby

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!