1

I'm trying to implement a simple design goal, but the complexity of Scala's type system gives me some headache. After a comparison of Traversable, Iterator, Iterable, Stream, View etc my decision is to define a custom trait (let's just call it Stream for brevity) that

  • is non-generic (my stream semantically only makes sense as some Stream[StreamEntry] and I want to avoid meaningless types like Stream[Int])
  • has similar usage to Iterable
  • all members like take, drop, etc should return Stream and not the basic Iterable.

This is what I have tried so far:

Approach 1

To sketch the use case, a simple example (which violates the third design goal) would be:

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 calls for the usage of a template trait instead of the base trait (I hope this is the standard terminology to refer to either SomeCollection or SomeCollectionLike). This means I have to use IterableLike[StreamEntry, Stream] which redefines the return types of the representing collection just as Iterable extends IterableLike[A, Iterable[A]] to return Iterables. My idea was to do pretty much the same as Iterable does. This would be:

// 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 template argument to GenericTraversableTemplate and the compiler now requires a template argument (exactly one) for Stream itself, which makes sense.

Approach 3, 4, ...

Starting from here, I got lost in the type system. Just removing with GenericTraversableTemplate leads to an incompatible type of newBuilder and an illegal inheritance due to conflicts in the type parameters in GenericTraversableTemplate from GenInterable and Traversable.

Maybe 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 the Builder. Is it possible to reuse a generic Builder for my non-generic trait? Actually I though I can go without a Builder because I never actually want to build a new Stream from other collections. But currently I'm experiencing some strange runtime behavior with this approach, which I cannot fully understand. For instance:

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 somewhat lost in the depth of the Scala type system. Am I still on the right track with this last approach and is the Builder just the missing piece in this puzzle?

And how would a Builder implementation look like for a non-generic non-cached type? A straightforward idea to implement += would be to use some mutable Buffer, but this would be very much against the use Iterators in the first place... And how should I implement the to member if I don't know how to construct a class of that type? I guess all relevant code must be somewhere in the library, I just can't dig it out.

bluenote10
  • 23,414
  • 14
  • 122
  • 178
  • You should be aware `Stream` is a built-in type and thus that name should not be re-defined in your own code. It will confuse those who are more familiar with the Scala standard library. – Randall Schulz Feb 11 '13 at 15:15
  • I know and I was considering to rename it to `MyStream` for this posting but I though it is equally clear to mention that I will just call my custom trait `Stream` in the following. – bluenote10 Feb 11 '13 at 15:21
  • You should take a look at the implementation of StringOps and WrappedString the standard libraries. – Ptharien's Flame Feb 11 '13 at 23:43
  • @Ptharien'sFlame: I did investigate this but if I would use a Builder similar to the underlying builder in these cases (a heavily mutable Java StringBuilder) I'll just end up with what I do not want: Calling for instance 'drop' would store the remainder of the whole stream in memory. My feeling now is that this can only be solved by non-builder-based collections, which means Iterable is not the way to go. – bluenote10 Feb 13 '13 at 09:26

1 Answers1

2

Wow! You've got a lot going on there...

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

Terminology:

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

Design & Language Factors:

You say:

… is non-generic (my stream semantically only makes sense as some Stream[StreamEntry] and I want to avoid meaningless types like Stream[Int])

The requirement does not argue for a non-generic class. Rather it is the essence of what a "type bound" is. E.g.:

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

In this case, class Generic may only be instantiated with types that are subtypes of UpperBound. (Note that whenever we say "subtype" we mean a reflexive subtype relationship. In other words, every type is a subtype of itself under this definition.

The upshot:

I wonder what your "stream" class is or does or has that is not satisfied by an existing type in the Scala Standard Library? As you've discovered extending the standard library collection classes is not entirely trivial, though it certainly is doable. I think doing so is not an elementary exercise in Scala programming and probably shouldn't be attempted as one of your first forays into Scala.

(*) This is an oversimplification that suffices for the purpose of this explanation.

Randall Schulz
  • 26,420
  • 4
  • 61
  • 81
  • +1 for these good points. Regarding Terminology: I was just relying on the main difference in the scaladoc formulation "Iterable is a base trait for..." vs "IterableLike is a template trait for...", but you are right "parameterized types" is also much more in line with my thinking. You're also right with your design hint. On the other hand, a "keep it simple" solution also has its advantages (not having to think about type parameters at all; introducing proper type bounds is more the other extreme). It's quite a surprise that the non-generic version is actually more complex... – bluenote10 Feb 11 '13 at 16:05
  • and to be honest: My design goals are partially educational :). After 1.5 years of *using* Scala it might be a thing to actually *learn* it... And probably avoiding extending the standard library forever won't help to this end :). – bluenote10 Feb 11 '13 at 16:08
  • what the my stream does: Not much. It mainly contains meta information about the stream. In principle it is possible to store this information separately and relying on for instance `Iterable[StreamEntry]` for the iteration. But it would be much more convenient, if the meta data is directly available in the stream even after applying 'take', 'drop', etc. Furthermore, some operations might change the meta info. If it is stored separately such a change is inconvenient. – bluenote10 Feb 11 '13 at 16:20