Ruby的可枚举类不能保持相同的类。

问题描述 投票:4回答:4

我有一个代表集合的类。我加入了 Enumerable 模块中,并定义了方法 #each这样我就可以得到它的所有方法。

但问题是 Enumerable'的方法不保持同一个类。所以,举例来说,如果我的类名为 Collection如果我这样做 Collection#select,我希望结果的类也是 Collection (而不是 Array). 有什么方法可以实现这个目标吗?

ruby enumerable
4个回答
3
投票

不幸的是,Ruby的集合操作不是类型保存的。每个集合操作总是返回一个 Array.

对于收藏品,如 SetTrees,这仅仅是烦人的,因为你需要总是将它们转换回你想要的类型。但是对于一个无限的所有质数的懒惰流来说,这是灾难性的:你的程序要么挂起,要么耗尽内存,试图构造一个无限大的 Array.

大多数Collection API要么消除重复代码,要么保留类型,但不是两者兼而有之。例如,.NET的Collection API大多消除了重复的代码,但它总是返回相同的类型。IEnumerable (相当于Ruby的 Enumerator). Smalltalk的Collection API是保留类型的,但它是通过在每个Collection类型中重复所有的Collection操作来实现的。

唯一一个既保留类型又消除重复的Collection API是Scala的。它通过引入新的概念,即 收集建设者,它知道如何有效地构造一个特定类型的集合。Collection Operations是以Collection Builders来实现的,只有Collection Builders需要被复制......但无论如何,这些都是每个Collection所特有的。

如果你想在Ruby中使用类型保存的Collection操作,你需要在自己的Collection中复制所有的Collection操作(这将仅限于你自己的代码),或者重新设计整个Collection API来使用Builders(这不仅需要重新设计你自己的代码,还需要重新设计现有的Collection,包括每一个第三方的Collection)。

很明显,第二种方法即使不是不可能,至少也是不切实际的。不过第一种方法也有它的问题。集合操作是... 预期 返回 Array的,违反这个期望可能会破坏其他人的代码!

你可以采取类似于Ruby 2.0的懒惰收集操作的方法:你可以添加一个新方法 preserve_type 到你的API中,它返回一个具有类型保存的集合操作的代理对象。这样一来,在代码中就会清楚地标明与标准API的不同。

c.select …               # always returns an Array

c.preserve_type.select … # returns whatever the type of c is

就像:

class Hash
  def preserve_type
    TypePreservingHash.new(self)
  end
end

class TypePreservingHash
  def initialize(original)
    @original = original
  end

  def map(*args, &block)
    Hash[@original.map(*args, &block)
    # You may want to do something more efficient
  end
end

5
投票

Since Enumerable#select 被设计成返回一个数组,你需要告诉某个地方如何将其映射到一个 Collection 实例。这意味着,您需要明确定义 Collection#select. 否则,Ruby将无法从原始数组结果中知道映射规则的 Enumerable#selectCollection 实例。


2
投票

另一种方法是将Collection作为底层数组的代理。

class Collection
  def initialize( items= nil )
    @items = items || []
  end

  def respond_to_missing?(method_name, include_private = false)
    Enumerable.instance_methods.include? method_name
  end

  def method_missing name, *args, &block
    if @items.respond_to? name
      res = @items.send name, *args, &block
      res.kind_of?( Array ) ? Collection.new(res) : res
    else
      super
    end
  end
end

的代理。

col = Collection.new [1,2,3]
=> #<Collection:0x0000010102d5d0 @items=[1, 2, 3]>
col.respond_to? :map
=> true
col.map{|x| x * 2 }
=> #<Collection:0x000001009bff18 @items=[2, 4, 6]>

1
投票

下面的方法对我很有效。我发现只有过滤方法需要重新定义。如果我们重新定义所有返回 Array这包括 collect 不应重新定义。

  include Enumerable

  def select(&block)
    self.class.new(super.select(&block))
  end

  def reject(&block)
    self.class.new(super.reject(&block))
  end
© www.soinside.com 2019 - 2024. All rights reserved.