Reputation: 9407
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.
Upvotes: 0
Views: 171
Reputation: 114158
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]
Upvotes: 2
Reputation: 121000
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
.
Upvotes: 3