不幸的是,Ruby 的集合操作不是类型保留的。每个收集操作总是返回一个Array。
对于像Sets 或Trees 这样的集合,这只是烦人的,因为您需要始终将它们转换回您想要的类型。但例如对于所有素数的无限惰性流,这是灾难性的:您的程序将挂起或内存不足,试图构造一个无限大的Array。
大多数集合 API 要么消除重复代码,要么保留类型,但不能同时使用两者。例如。 .NET 的 Collection API 主要消除了重复代码,但它总是返回相同的类型:IEnumerable(相当于 Ruby 的 Enumerator)。 Smalltalk 的 Collection API 是类型保留的,但它通过在每个 Collection 类型中复制所有 Collection 操作来实现这一点。
唯一可以保留类型但消除重复的 Collection API 是 Scala 的。它通过引入 Collection Builders 的新概念来实现这一点,它知道如何有效地构造特定类型的 Collection。集合操作是根据集合构建器实现的,只有集合构建器需要复制……但无论如何,这些操作都是特定于每个集合的。
如果您想在 Ruby 中保留类型的集合操作,您需要在自己的集合中复制所有集合操作(这将仅限于您自己的代码),或者重新设计整个集合 API 以使用构建器(这需要不仅对您自己的代码进行了重大重新设计,而且对现有的集合(包括曾经编写的每个第三方集合)进行了重大重新设计。
很明显,第二种方法即使不是不可能,至少也是不切实际的。不过,第一种方法也有问题:集合操作预期返回Arrays,违反预期可能会破坏其他人的代码!
您可以采用类似于 Ruby 2.0 的惰性收集操作的方法:您可以向 API 添加一个新方法 preserve_type,该方法返回一个具有类型保留的收集操作的代理对象。这样一来,代码中就清楚地标出了与标准 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