Lokesh Lokesh - 6 months ago 11
Ruby Question

implement `each` for Enumerable mixin in Ruby

I'm learning about the magic of Enumerable in Ruby. I've heard one have to just include

Enumerable
and implement
each
method and can have the power of Enumerable for that class.

So, I thought about implementing my own custom class Foo for practice. It looks like below:

class Foo
include Enumerable

def initialize numbers
@num = numbers
end

def each
return enum_for(:each) unless block_given?
@num.each { |i| yield i + 1 }
end
end


This class takes an array and its
each
works almost similar to
Array#each
. Here's the difference:

>> f = Foo.new [1, 2, 3]
=> #<Foo:0x00000001632e40 @num=[1, 2, 3]>
>> f.each { |i| p i }
2
3
4
=> [1, 2, 3] # Why this? Why not [2, 3, 4]?


Everything works like I expect except one thing which is the last statement. I know its the return value but shouldn't it be
[2, 3, 4]
. Is there a way to make it
[2, 3, 4]
.

Also please comment on the way I have implemented
each
. If there's a better way please let me know. At first in my implementation I didn't had this line
return enum_for(:each) unless block_given?
and then it wasn't working when no block was provided. I borrowed this line from somewhere and also please tell me whether this is the right way to handle the situation or not.

Answer

each does not modify an array. If you want to return modified array, use map:

def each
  return enum_for(:each) unless block_given?
  @num.map { |i| yield i + 1 }
end
f.each { |i| p i }
2
3
4
=> [2, 3, 4]

But I recommend to use each inside a custom method. You can increment each element of your array by 1 in initialize method, since you want to use it for all calculations. Also, you can modify your each method to avoid using enum_for by passing block_given? inside a block. Finally your code will look like:

class Foo
  include Enumerable

  def initialize(numbers)
    @num = numbers.map {|n| n + 1 }
  end

  def each
    @num.each { |i| yield i if block_given? }
  end
end

f = Foo.new [1, 2, 3]
=> #<Foo:0x00000000f8e0d0 @num=[2, 3, 4]>
f.each { |i| p i }
2
3
4
=> [2, 3, 4]
Comments