Saturday, 6 June 2009

arrayify!

Following from my post: Array.acts_like_array?, I can now implement arrayify without using is_a? Array or respond_to? :[] && !is_a? String

class Object
  def arrayify
    self.acts_like?(:array) ? self : [self]
  end
end

So we can now do: thing = thing.arrayify or: do_something(thing.arrayify)

Now, what I really want to do is thing.arrayify!, but I'm not sure how to implement that. Any ideas?

11 comments:

Mark Tucker said...
This comment has been removed by the author.
Rob Anderton said...

Hi,

Although it is _slightly_ different, any reason why you wouldn't just do: thing = Array(thing) or do_something(Array(thing)) ?

...although 'arrayify' is a fun name for a method :D

Rob

Be4all said...

@Rob because if 'thing' is an array will not have the same result.

Rob Anderton said...

How so?

thing = [1,2,3]

thing.arrayify
=> [1,2,3]

Array(thing)
=> [1,2,3]

I must be missing something obvious!

Taryn East said...

doh! you're quite right.
I was under the assumption that ['x'].to_a would yield [['x']].

Of course - a quick check via irb and I was wrong. Ah well... arrayify is fun name though :)

still, I would like a ! equivalent (to_a! perhaps) (ie one that modifies in-place). I'm not sure how you do that as you can't set self= inside a method.

Taryn East said...

I retract my previous comment. I just found a counter-example:

{:x => 1, :y => 1}.to_a
=> [[:x, 1], [:y, 1]]


{:x => 1, :y => 1}.arrayify
=> [{:x => 1, :y => 1}]

Dave Burt said...

For the !-suffixed version, would you expect the object to change class? That would usually be a bad thing. You can do it, but only using the Evil library or other serious and unportable hackery.

require 'evil'
class Object
def arrayify!
become Array
replace! arrayify
end
end

Taryn East said...

@Dave - you're right it is partly evil as it changes the type of the object.

Though what you can guarantee is that at the end-point - the object will be guaranteed to have a given type (in this case, an Array). Which is stability of a sort.

This is a different use-case to, say to_a where the resulting object maybe be an array, but you wouldn't want to alter the original variable because you're fundamentally changing the shape of the object.

with to_a you are coercing the thing (whatever it is) into an Array - probably destroying the original object in the process. In this case you still have the original object. Either it is an array already (and so no change) or it becomes an array with the original thing in it...

IMO this makes it less evil than, say, some kind of Hash.to_a!

:)

Dave Burt said...

It seems like you are missing something about Ruby's type system. Duck typing means many objects can produce versions of themselves as other types, but it is also strongly typed, so any given object will remain that kind of object forever, unless you subvert Ruby itself. To_a, coerce and friends never change the type of an object, they just return another. An implementation that destroyed the original object in the process (of, for instance, producing an array) would be broken.
There's a big difference between "the object becomes" and "the object's instance method returns".

Taryn East said...

Hi Dave,

I haven't missed that, just grumbling about it when it gets in my way :)

I agree that it's totally evil to want something different for the tiny percentage of special cases where I'd actually use it - and I don't know that it really would make the code look better if I did. Just thought it'd be interesting to try and then got curious about how I'd go about doing it if I did.

Right now I'm perfectly able to do something like:

def some_method(some_arg)
some_arg.arrayify.each {|n| n.do_something }
end

Which is what I would usually want. I was just hoping to do something like:


def some_method(some_arg)
some_arg.arrayify!

some_other_stuff if some_condition(some_arg)

some_arg.each {|n| n.do_something }
end

etc etc :)

Obviously the above code is *extremely* simplified...

Dave Burt said...

That's even worse -- a method that changes the class of a passed parameter as a potentially unintended side-effect!
The #become method makes it easy to write some surprising code.
Perhaps you can accomplish similar things by boxing your object in an array or something, then you can use #replace to swap in other objects freely. I recall some related discussion on a Ref type/class on ruby-talk back in the day, you might enjoy reading some of that.