Scala Inheritance; Problems with Builder; Nontrivial IterableLike

I am trying to realize a simple project goal, but the complexity of a system like Scala gives me some headache. After comparing Traversable, Iterator, Iterable, Stream, View, etc. My solution is to define a custom attribute (just call it Stream for short), which

  • is nonequivalent (my stream semantically only makes sense like some Stream[StreamEntry] , and I want to avoid meaningless types like Stream[Int] )
  • has a similar use for Iterable
  • all members, such as take , drop , etc., should return a Stream , not the underlying Iterable .

This is what I have tried so far:

Approach 1

To outline a usage example, a simple example (which violates the third design goal):

 case class StreamEntry(data: Double) // just a dummy trait Stream extends Iterable[StreamEntry] { val metaInfo: String } // example use case val s = new Stream { val metaInfo = "something" val iterator = StreamEntry(1) :: StreamEntry(2) :: StreamEntry(3) :: Nil toIterator } val t = s.take(1) // unfortunately, this is no longer a Stream 

Approach 2

This third requirement requires the use of a template tag instead of a base tag (I hope this is the standard terminology for referring to SomeCollection or SomeCollectionLike). This means that I have to use IterableLike[StreamEntry, Stream] , which overrides the return types of the representing collection in the same way as Iterable extends IterableLike[A, Iterable[A]] to return Iterable s. My idea was to do almost the same thing as Iterable . This will:

 // this is exactly the way `Iterable` is defined, but non-generic trait Stream extends Traversable[StreamEntry] with GenIterable[StreamEntry] with GenericTraversableTemplate[StreamEntry, Stream] with IterableLike[StreamEntry, Stream] { ... } 

Unfortunately, this does not compile, because Stream appears as a GenericTraversableTemplate argument to GenericTraversableTemplate , and now the compiler requires a template argument (exactly one) for Stream , which makes sense.

Approach 3, 4, ...

From this point on, I got lost in the type system. Just removing with GenericTraversableTemplate results in an incompatible newBuilder type and inappropriate inheritance due to conflicts in the type parameters in the GenericTraversableTemplate of GenInterable and Traversable .

Perhaps the closest solution was the following:

 trait Stream extends TraversableLike[StreamEntry, Stream] with IterableLike[StreamEntry, Stream] { val metaInfo: String def seq = this def newBuilder: scala.collection.mutable.Builder[StreamEntry, Stream] = ??? } 

This compiles, but unfortunately I have no idea how to implement Builder. Can I reuse generic builder for my non-generic trait? In fact, although I can do without Builder, because I never want to create a new Stream from other collections. But at the present time I am experiencing some strange behavior while working with this approach, which I cannot fully understand. For example:

 val s = new Stream { val metaInfo = "something" val iterator = StreamEntry(1) :: StreamEntry(2) :: StreamEntry(3) :: Nil toIterator } // executing the following independently (not in sequence) results in: s.take(1) // throws: scala.NotImplementedError: an implementation is missing // seems to require a Builder :( s.toArray // works s.toIterator // works s.toIterable // throws: java.lang.ClassCastException: cannot be cast to scala.collection.Iterable 

Now I feel a little lost deep in a system like Scala. Am I still on the right track with this latest approach and is Builder just the missing piece in this puzzle?

And what would the Builder implementation look like for a nonequivalent type without caching? The direct idea to implement += would be to use some mutable Buffer, but that would be very strong against using Iterators in the first place ... And how do I implement the to element if I don't know how to build a class of this type? I assume that all the relevant code should be somewhere in the library, I just can’t dig it out.

+1
iterator inheritance scala builder
Feb 11 '13 at 15:04
source share
1 answer

Wow! You have a lot of things there ...

Here are some things you should know or consider when solving this design problem ...

Terminology:

  • We do not refer to the "template", we call them "generic" or "parameterized" types. The reason for this is that these types are not patterns! That is, they are not filled with their actual type parameters to create new classes every time they are used (as is the case with C ++, which rightly uses the term "template"). Instead, only one class (*) is created, and it serves for each instance of this generic type with specific type parameters.

Design and language factors:

You speak:

... is not general (my stream semantically only makes sense as some stream [StreamEntry], and I want to avoid meaningless types such as Stream [Int])

The requirement does not prove to be a non-general class. Rather, this is the essence of what a "related type" is. For example:.

 class Generic[T <: UpperBound](ctorArgs...) { } 

In this case, the Generic class can only be created with types that are subtypes of UpperBound . (Note that whenever we say "subtype", we mean the reflexive relation of the subtype. In other words, each type is a subtype of itself within the framework of this definition.

Result:

Interestingly, does your "stream" class have or does not have or does not satisfy an existing type in the Scala standard library? Since you opened the extension of the standard library collection classes, this is not entirely trivial, although it is certainly doable. I think this is not an elementary exercise in Scala programming and probably should not be tried as one of your first raids on Scala.

(*) This is a simplification sufficient for the purpose of this explanation.

+2
Feb 11 '13 at 15:36
source share



All Articles