Unfortunately, Ruby Collection operations are not stored in the type. Each collection operation always returns an Array .
For collections like Set or Tree s, this is just annoying because you need to always convert them back to the type you want to have. But, for example, for an endless lazy stream of all primes this is catastrophic: your program either freezes or runs out of memory, trying to build an infinitely large Array .
Most Collection APIs either eliminate duplicate code or retain the type, but not both. For instance. The API.NET Collection basically eliminates duplicate code, but it always returns the same type: IEnumerable (equivalent to Ruby Enumerator ). The Smalltalk Collection API is saved in the type, but it does this by duplicating all collection operations in each type of collection.
The only Collection API that preserves the type but eliminates duplication is Scala. He accomplishes this by introducing a new concept for collectors who can efficiently create a collection of a certain type. Operations with collections are implemented from the point of view of collectors, and only collectors of collections need to be duplicated ... but they are still specific to each collection.
If you want collection operations with type preservation to be stored in Ruby, you need to either duplicate all collection operations in your own collection (which will be limited by your own code), or redesign the entire Collection API to use Builders (which will require a basic redesign not only of yours own code, but also existing Collections, including every third-party collection ever written).
Clearly, the second approach is at least impractical, if not impossible. The first approach also has its problems, though: it is expected that operations with collections will return Array s, violating this expectation, can break other people's code!
You can use an approach similar to Ruby 2.0 lazy collection: you can add a new preserve_type method to your API that returns a proxy object containing Collection Operations. Thus, the departure from the standard API is clearly indicated in the code:
c.select β¦
Sort of:
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)