残念ながら、Rubyのコレクション操作は型を保持していません。すべての収集操作は常にを返しますArray
。
Set
sやsのようなコレクションの場合Tree
、これは単に煩わしいだけです。なぜなら、それらを常に必要なタイプに戻す必要があるからです。しかし、たとえば、すべての素数の無限の遅延ストリームの場合、これは壊滅的です。プログラムがハングするか、無限に大きなを構築しようとしてメモリが不足しますArray
。
ほとんどのコレクションAPIは、重複するコードを排除するか、型を保持しますが、両方を保持することはできません。たとえば、.NETのコレクションAPIはほとんどの場合重複コードを排除しますが、常に同じタイプを返しますIEnumerable
(Rubyと同等Enumerator
)。SmalltalkのコレクションAPIはタイプを保持しますが、すべてのコレクションタイプのすべてのコレクション操作を複製することでこれを実現します。
型を保持しながら重複を排除する唯一のCollectionAPIはScalaのものです。これは、特定のタイプのコレクションを効率的に構築する方法を知っているコレクションビルダーの新しい概念を導入することによって実現されます。コレクション操作はコレクションビルダーの観点から実装され、コレクションビルダーのみを複製する必要があります…しかし、それらはとにかくすべてのコレクションに固有です。
Rubyで型を保持するコレクション操作が必要な場合は、独自のコレクション内のすべてのコレクション操作を複製するか(独自のコードに限定されます)、ビルダーを使用するようにコレクションAPI全体を再設計する必要があります(大幅な再設計が必要になります)。独自のコードだけでなく、これまでに作成されたすべてのサードパーティコレクションを含む既存のコレクションも含まれます)。
2番目のアプローチは、不可能ではないにしても、少なくとも実用的ではないことは明らかです。ただし、最初のアプローチにも問題があります。収集操作はsを返すことが期待されArray
ており、その期待に違反すると他の人のコードが破損する可能性があります。
Ruby2.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