The object model in Ruby is simple, single inheritance but with ability to "mixin" modules to add shared behavior. In your case you are using the select
method which exists in the module Enumerable
. This module is mixed into Array, Hash, and Range. This gives instances of those classes methods such as select
. You can read more about enumerable methods here: https://ruby-doc.org/core-2.2.3/Enumerable.html#method-i-select
If you think about it, it makes sense that Range#select
returns an Array. You're not selecting contiguous values from the range are you? You're selecting arbitrary values from which the block returns true, this makes it impossible to return a range therefore, #select
will always return an array even if it's called on a Hash or any other Class that mixes in Enumerable.
Update:
To understand how Enumerable is returning an Array from a Range
To implement any classes that mix in Enumerable
you only have to define the #each
method on your class. Say you hypothetically re-implemented Range:
class Range
include Enumerable # mixin
def initialize(first, last)
@number_range = first.upto last # this is an array of ints
end
def each(&block) # this methods returns an enumerable
@number_range.each &block
end
end
With the above we can initialize our hypothetical range instance:
@hypo_range = Range.new 1, 10
And call enumerable methods on it:
@hypo_range.any? { |i| i == 5 } # => true
@hypo_range.select &:odd? # => [1,3,5,7,9]
Because you need only implement #each
to hook into the Enumerable API, Ruby knows exactly what to do with it no matter what the class of the object is. This is because in your new #each
method you are iterating over an array already! Enumerable uses your each
method under the hood to implement all the other enumerable methods on top e.g. any?
, select
, find
, etc.
That #each
method is where you tell Ruby how to iterate over your collection of objects. Once Ruby knows how to iterate over your objects the results are already an Array.
Rubinius implementation of Range
You can see here that Range is implemented by using while
to loop from the first
value until it reaches the last
value and yield
ing to the block on each iteration. The block collects the results into an Array and that's how you get the Array out of calling Range#select
because select
is using that each
under the hood.
https://github.com/rubinius/rubinius/blob/master/core/range.rb#L118
Some resources: