0

I was experimenting with Enumerable and Comparable. I read the documentation and wanted to try it out.

class Apple
  attr_accessor :color
end

class Fruit
  include Enumerable
  include Comparable

  attr_accessor :apples

  def initialize
    @apples = []
  end

  def <=> o
    @apple.color <=> o.color
  end

  def each
    @apples.each {|apple| yield apple }
  end

  def to_s
    "apple: #{@apple.color}"
  end
end

fruit = Fruit.new
a1 = Apple.new
a1.color = :red
a2 = Apple.new 
a2.color = :green
a3 = Apple.new
a3.color = :yellow
fruit.apples.push a1
fruit.apples.push a2
fruit.apples.push a3

Two things are not working as expected. So I override to_s, I expect each index of array to contain a string like "apple: red". Instead I get this:

fruit.sort
 => [#<Apple:0x007fbf53971048 @apples=[], @color=:green>, #<Apple:0x007fbf53999890 @apples=[], @color=:red>, #<Apple:0x007fbf5409b530 @apples=[], @color=:yellow>]

Second issue is when I include Enumerable, the instance methods of Enumerable should have been added to the ancestor chain right before the inherited classes. This should have included methods of Enumerable like with_each, reduce, etc to the ancestor chain. However, when I do this:

fruit.each.with_index(1).reduce({}) do |acc,(apple,i)|
  acc << { i => apple.color}
end
LocalJumpError: no block given (yield)

as you can see, I get a LocalJumpError. I expected a result like this:

{ 1 => :red, 2 => :green, 3 => :yellow}

What am I doing wrong? I defined each like I was supposed to yet it doesn't work as expected.

1
  • Your naming convention is a bit odd. Most people would consider an apple to be a fruit. Having a fruit that can contain apples is quite confusing. Commented Jun 15, 2018 at 6:42

2 Answers 2

3

Return an Enumerator::Lazy#enum_for when no block is given:

def each
  @apples.each
end

Array does that on it’s own, hence the above is already possible. The code inside is effectively similar to:

def each
  return enum_for(:each) unless block_given?

  @apples.each { |apple| yield apple }
end

What you see in pry/irb is the result of inspect, not to_s.

Sign up to request clarification or add additional context in comments.

Comments

2

I override to_s, I expect each index of array to contain a string like "apple: red". Instead I get this: ...

Two things are wrong here.

1) you have to implement Apple#to_s, not Fruit#to_s:

class Apple
  attr_accessor :color

  def to_s
    "apple: #{color}"
  end
end

2) you have to implement inspect or define it as an alias:

class Apple
  attr_accessor :color

  def to_s
    "apple: #{color}"
  end
  alias inspect to_s
end

This will give you:

fruit = Fruit.new
a1 = Apple.new
a1.color = :red
a2 = Apple.new
a2.color = :green
a3 = Apple.new
a3.color = :yellow
fruit.apples.push a1
fruit.apples.push a2
fruit.apples.push a3

fruit
#=> #<Fruit:0x00007faa3686b7c0 @apples=[apple: red, apple: green, apple: yellow]>

Second issue is when I include Enumerable, the instance methods of Enumerable should have been added to the ancestor chain ...

When you write:

fruit.each.with_index(1)

you are calling with_index on the return value of each. That's where the error occurs:

fruit.each
#=> LocalJumpError: no block given (yield)

You have to return an instance of Enumerator when no block is given. This can be achieved using a conditional (see mudasobwa's answer) or by passing the block along:

def each(&block)
  @apples.each(&block)
end

There's another issue with your code: not Fruit but Apple is the class that should implement <=> and include Comparable. Because when sorting @apples, the items are being compared to each other:

class Apple
  include Comparable

  attr_accessor :color

  def <=> o
    color <=> o.color
  end

  # ...
end

Note that there's a catch when including Enumerable. Although you are able to use all those methods, you can easily lose your wrapping class and end up with a plain array:

fruit
#=> #<Fruit:0x00007faa3686b7c0 @apples=[apple: red, apple: green, apple: yellow]>

fruit.sort
#=> [apple: green, apple: red, apple: yellow]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.