5

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
  • perfect, thanks, this is just what I need at this second. Commented Oct 24, 2009 at 18:45

3 Answers 3

7

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Subtle indeed; this is similar distinction between == and eql? which I really don't like. Why is a good idea to have to_a AND to_ary ???
to_ary is for objects that are effectively arrays. If something that really needs to use an array can use your object instead of an array and your object is meant to be an array, then it should implement to_ary. to_a is for things that are convertible to arrays, e.g. objects with (terminating) each methods have an obvious implementation of to_a. E.g. a file could have a to_a method, but you can't treat it as an array, and it doesn't model an array so it wouldn't have a to_ary method.
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.

2 Comments

This is somewhat helpful, but I hate it when I ask a question and people believe I have no good reason to do what I'm trying to do. If you think this is a bad idea, explain why!! I appreciate your examples, but showing typical uses doesn't explain what's wrong with what I'm trying to do :/
I didn't say you didn't have a good reason, I just said I don't know what your reason is. I can't voice an opinion on whether it's good or not until I know what it is. I certainly can't say there is never a circumstance where you'd want to do this, but if you'd indulge my curiosity I'd appreciate it.
1

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.

2 Comments

I think you meant to put brackets around "a", "b", "c" in your first example. When I tried it, I got a syntax error.
Actually I forgot the "return". Fixed now.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.