First off, Step
looks perfect to me. As for Stream
, I think you're on the right track with the abstract type. Here's what I came up with (including implementations of the remaining methods in section 2.1 of the Coutts paper):
abstract class Stream[A] {
protected type S
def next: S => Step[A, S]
def state: S
def map[B](f: A => B): Stream[B] = {
val next: S => Step[B, S] = this.next(_) match {
case Done => Done
case Skip(s) => Skip(s)
case Yield(a, s) => Yield(f(a), s)
}
Stream(next, state)
}
def unstream: List[A] = {
def unfold(s: S): List[A] = next(s) match {
case Done => List.empty
case Skip(s) => unfold(s)
case Yield(a, s) => a :: unfold(s)
}
unfold(state)
}
}
object Stream {
def apply[A, S0](n: S0 => Step[A, S0], s: S0) = new Stream[A] {
type S = S0
val next = n
val state = s
}
def apply[A](as: List[A]): Stream[A] = {
val next: List[A] => Step[A, List[A]] = {
case a :: as => Yield(a, as)
case Nil => Done
}
Stream(next, as)
}
def unapply[A](s: Stream[A]): Option[(s.S => Step[A, s.S], s.S)] =
Some((s.next, s.state))
}
A couple things to note:
- My
unapply
has a dependent method type: it depends on thes.S
. I think that might have been your stumbling block. - The
unfold
method inunstream
is not tail-recursive.
The thing I'm still not really clear on myself is why it's important for S
to be existential / hidden / whatever. If it's not, you could just write:
case class Stream[A, S](next: S => Step[A, S], state: S)
... but I assume there's a reason for it. That being said, I'm also not sure this approach actually hides S
the way you want. But this is my story and I'm sticking to it.