Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby Integer(), Array(), et al -- what are they? Where do they come from?

I've come across conversions of the form Array(value), String(value), and Integer(value) on occasion. It appears to me that these are just syntactic sugar for a call to the corresponding value.to_a, value.to_s, or value.to_i methods.

So I'm wondering:

  • Where/how are these are defined? I can't find them in Object, Module, Class, etc
  • Are there any common scenarios for which it's preferable to use these rather than the corresponding/underlying to_X method?
  • Could these be used in type-generic coercion? That is, can I do something along the lines of

    [Integer, String, Array].each {|klass| klass.do_generic_coercion(foo) }
    

? (...and no, I don't really want to do that; I know the type I want out, but I'm looking to avoid the case statement.)

like image 447
dondo Avatar asked Jan 11 '10 00:01

dondo


People also ask

What are arrays in Ruby?

Ruby arrays are ordered, integer-indexed collections of any object. Each element in an array is associated with and referred to by an index. Array indexing starts at 0, as in C or Java.

How do you create an array of objects in Ruby?

You can create an array by separating values by commas and enclosing your list with square brackets. In Ruby, arrays always keep their order unless you do something to change the order. They are zero-based, so the array index starts at 0.

How can you test if an item is included in an array Ruby?

This is another way to do this: use the Array#index method. It returns the index of the first occurrence of the element in the array. This returns the index of the first word in the array that contains the letter 'o'. index still iterates over the array, it just returns the value of the element.


2 Answers

This is a good and difficult question. Let's answer the three parts.

First part

To find the definition, it is important to realize that the name of the method is "Array", etc., which can be quite counterintuitive, since methods are usually lowercase...

irb> method(:Array)
=> #<Method: Object(Kernel)#Array>

This tells you these are defined in Kernel, and thus available everywhere without requiring an explicit prefix.

Second part

Array(), String(),... are conversion methods. Calling obj.to_a will return an array, but will raise an NoMethodError if obj doesn't respond_to? :to_a. So the typical case when you'd prefer using Array(), String(), instead of to_a or to_s is when you are not positive an object responds to a given conversion method.

String(obj) will return nil if obj doesn't respond_to? :to_s. String(obj) will also check that the result of to_s is actually a string; it should be, but maybe an overly creative programmer decided to return something else?

Most other conversion methods act the same way, but Array(obj) is different. It will return [obj] if obj doesn't respond_to? :to_a. It will actually call to_ary (which is the implicit conversion operation, while to_a is the explicit one).

There is another important way to convert objects in 1.9 (and upcoming 1.8.8): Array.try_convert(obj). This returns nil if the obj does not respond_to? :to_ary. It will not call the to_a. Although they are longer to type, you might prefer using them when writing very general code that might accept different types of objects and want to avoid converting a hash to an array by mistake, for example (since Hash has a to_a method but not to_ary). When your method requires an array-like object and you are willing to do an explicit conversion, then obj.to_a is fine. The typical use of Array(obj) would be in a method that accepts either a single obj to act on, or a list of objects (although typically this is written as [*obj]).

Last part

Hopefully, the answers to the first two parts give you your final answer...

You can use:

[Integer, String, Array].each {|klass| klass.try_convert(foo) }

or

[:Integer, :String, :Array].each{|method| send(method, obj)}
like image 53
Marc-André Lafortune Avatar answered Nov 13 '22 06:11

Marc-André Lafortune


Good question! Let's see if we can figure it out.

Ross-Harveys-MacBook-Pro:ruby-1.9.1-p376 ross$ irb
irb(main):001:0> Object.ancestors
=> [Object, Kernel]
irb(main):002:0> Kernel.ancestors
=> [Kernel]
irb(main):003:0> Kernel.class
=> Module
irb(main):004:0> Kernel.public_methods.include? "Array"
=> true

So, it looks like these are methods in the Kernel module that are mixed in to Object, so they are available without specifying a receiver. We might also want to peek at the C implementation, in object.c:

VALUE
rb_Array(VALUE val)
{
    VALUE tmp = rb_check_array_type(val);

    if (NIL_P(tmp)) {
        tmp = rb_check_convert_type(val, T_ARRAY, "Array", "to_a");
        if (NIL_P(tmp)) {
            return rb_ary_new3(1, val);
        }
    }
    return tmp;
}

One thing seems easy to conclude, the default .to_a is deprecated, so it does seem like Array(x) is the canonical way to do the conversion. It apparently does nothing if given an Array, calls .to_a if that's present, and if not it just wraps its argument in an Array.

Regarding whether to_a is deprecated...well, I said "the default":

Ross-Harveys-MacBook-Pro:puppet_sd ross$ irb
irb(main):001:0> class X; X; end.new.to_a
(irb):1: warning: default `to_a' will be obsolete
like image 8
DigitalRoss Avatar answered Nov 13 '22 05:11

DigitalRoss