Reputation: 20493
I am following the book Functional programming in Scala
and in particular the section where you implement a simple Stream trait and companion object. For reference, here is what we have so far in the companion obejct
object Stream {
def empty[A]: Stream[A] =
new Stream[A] {
def uncons = None
}
def cons[A](hd: => A, tl: => Stream[A]): Stream[A] =
new Stream[A] {
lazy val uncons = Some((hd, tl))
}
def apply[A](as: A*): Stream[A] =
if (as.isEmpty)
empty
else
cons(as.head, apply(as.tail: _*))
}
and the trait so far:
trait Stream[A] {
import Stream._
def uncons: Option[(A, Stream[A])]
def toList: List[A] = uncons match {
case None => Nil: List[A]
case Some((a, as)) => a :: as.toList
}
def #::(a: => A) = cons(a, this)
def take(n: Int): Stream[A] =
if (n <= 0)
empty
else (
uncons
map { case (a, as) => a #:: (as take (n - 1)) }
getOrElse empty
)
}
The next exercise requires me to write an implementation for takeWhile
and I thought the following would do
def takeWhile(f: A => Boolean): Stream[A] = (
uncons
map { case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty }
getOrElse empty
)
Unfortunately, is seems that I get a variance error that I am not able to track down:
error: type mismatch; found : Stream[_2] where type _2 <: A
required: Stream[A]
Note: _2 <: A, but trait Stream is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
getOrElse empty
^
I could add a variance annotation, but before doing that I would like to understand what is going wrong here. Any suggestions?
Upvotes: 2
Views: 1491
Reputation: 11280
To complete a bit the other answer, the empty
on this line:
map { case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty }
is inferred as empty[Nothing]
, which means that (a #:: (as takeWhile f)) else empty
is inferred as Stream[Foo <: A]
and since a Stream[A]
is expected and Stream
is invariant, you have an error.
So this gives us the cleanest way to fix this: just annotate empty
:
map { case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty[A] }
And then it compiles fine.
This does not happen with the original Stream
because it is covariant, so either you actually want Stream.empty
to be a Stream[Nothing]
(just like Nil
is a List[Nothing]
), or you don't care.
Now, as to exactly why it is inferred as empty[Nothing]
and not empty[A]
, this is probably hidden somewhere in SLS 6.26.4 "Local Type Inference", but this part cannot really be accused of being easy to read...
As a rule a thumb, always be suspicious whenever you call methods:
Upvotes: 1
Reputation: 42047
this seems to be an issue with type inference, because it works if you explicitly specify the type of the subexpression uncons map { case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty }
.
def takeWhile(f: A => Boolean): Stream[A] = {
val mapped:Option[Stream[A]] = uncons map {
case (a, as) => if (f(a)) (a #:: (as takeWhile f)) else empty
}
mapped getOrElse empty
}
Upvotes: 2