在自定义类中使用Enumerable的方法

时间:2018-06-15 05:56:23

标签: ruby

我正在尝试使用Enumerable和Comparable。我阅读了文档并希望尝试一下。

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

有两件事没有按预期工作。所以我重写to_s,我希望数组的每个索引都包含一个类似&#34; apple:red&#34;的字符串。相反,我得到了这个:

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

第二个问题是当我包含Enumerable时,Enumerable的实例方法应该在继承的类之前添加到祖先链中。这应该包括Enumerable的方法,如with_each,reduce等等到祖先链。但是,当我这样做时:

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

如您所见,我得到一个LocalJumpError。我期待这样的结果:

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

我做错了什么?我定义了each,就像我应该的那样,它没有像预期的那样工作。

2 个答案:

答案 0 :(得分:3)

当没有给出阻止时返回Enumerator::Lazy#enum_for

def each
  @apples.each
end

Array就是这样做的,因此上面已经可以了。里面的代码实际上类似于:

def each
  return enum_for(:each) unless block_given?

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

您在pry / irb中看到的内容是inspect的结果,而不是to_s

答案 1 :(得分:2)

  

我重写to_s,我希望数组的每个索引都包含一个像“apple:red”这样的字符串。相反,我得到了这个:...

这里有两件事是错误的。

1)您必须实施Apple#to_s,而不是Fruit#to_s

class Apple
  attr_accessor :color

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

2)您必须实施inspect或将其定义为alias

class Apple
  attr_accessor :color

  def to_s
    "apple: #{color}"
  end
  alias inspect to_s
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

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

第二个问题是当我包含Enumerable时,Enumerable的实例方法应该被添加到祖先链......

当你写:

fruit.each.with_index(1)

您在with_index的返回值上调用each。这就是错误发生的地方:

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

当没有给出阻止时,您必须返回Enumerator的实例。这可以使用条件(参见mudasobwa's answer)或通过块传递来实现:

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

您的代码还有另一个问题:不是Fruit,而Apple是应该实施<=>并包含Comparable的类。因为在排序@apples时,项目会相互比较:

class Apple
  include Comparable

  attr_accessor :color

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

  # ...
end

请注意,包含Enumerable时有一个问题。虽然您可以使用所有这些方法,但您很容易丢失包装类并最终得到一个普通数组:

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

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