Ruby .times method called on an integer. How does it know to iterate through each integer as the argument in a block?

Viewed 287

I'm reading the "Pickaxe" ruby book and I came across this example:

def meth_three
  100.times do |num|
    square = num*num
    return num, square if square > 1000
  end
end

If you call meth_three in irb it returns the first integer between 1 and 100 that has a square > 1000:

meth_three # => [32, 1024]

My question is, how does the times method know how to loop through each integer between 1..100 to pass as the argument to the |num| parameter?

3 Answers

There are many, many different ways of writing iteration in Ruby.

Here is one possible way that it could be implemented recursively:

class Integer
  def times(&blk)
    return enum_for(__callee__) unless block_given?
    return self unless positive?

    pred.times(&blk)

    yield pred
  end
end

Or, using Range#each:

class Integer
  def times(&blk)
    return enum_for(__callee__) unless block_given?
    return self unless positive?

    (0...self).each(&blk)

    self
  end
end

Or, with a loop:

class Integer
  def times
    return enum_for(__callee__) unless block_given?
    return self unless positive?

    i = -1
    yield i while (i += 1) < self

    self
  end
end

And a tail-recursive implementation just for the fun of it:

class Integer
  def times(&blk)
    return enum_for(__callee__) unless block_given?
    return self unless positive?

    __times_rec(0, &blk)

    self
  end

  private def __times_rec(i, &blk)
    return unless i < self

    yield i

    __times_rec(i.succ, &blk)
  end
end

Here is the actual code from an actual Ruby implementation (core/integer.rb from Rubinius):

def times
 return to_enum(:times) { self } unless block_given?

 i = 0
   while i < self
     yield i
     i += 1
   end
 self
end

TruffleRuby uses the exact same code as well (see src/main/ruby/truffleruby/core/integer.rb), as does JRuby (see core/src/main/ruby/jruby/ruby_implementations/Integer/times.rb)

Here is another real example from a real Ruby implementation (opal/corelib/number.rb from Opal):

def times(&block)
 return enum_for(:times) { self } unless block

 %x{
   for (var i = 0; i < self; i++) {
     block(i);
   }
 }

 self
end

As you can see, there are many ways in which one could write a loop in Ruby.

Some of the comments hint at what is going on. I think it can be explained like this:

Each integer in Ruby is actually an object, more specifically an instance of the Integer class.

You can think of it as a Integer#times method, that could naively be implemented something like this:

class Integer
  def times(&block)
    if self.value > 0
      for i in 0...self.value do
        block.call(i)
      end
    end
  end
end

This method is actually implemented in C in the official version of ruby (MRI), as pointed out in one of the comments to your question. I only write something similar here to help explain the concept on how it could have looked in Ruby.

The times method actually returns a value, which is the current iterator with every loop it makes (1, 2, 3 ... 100).

The do keyword catches this iterator, and uses it as the variable num.

Anytime you see the num variable, you are seeing the current iterator that's being returned by times.

Related