I want to reimplement again scala left/right/either. Here is my code:
case class Left[+E](get: E) extends Either[E, Nothing]
case class Right[+A](get: A) extends Either[Nothing, A]
sealed trait Either[+E, +A] {
def orElse[EE >: E, B](b: => Either[EE, B]): Either[EE, B]
this match {
case Left(_) => b // error here
case Right(a) => Right(a)
}
}
I don't know what I meet error at case Left(_) => b:
Cannot resolve symbol b
But this method will not throw error:
def orElse[EE >: E, AA >: A](b: => Either[EE, AA]): Either[EE, AA] =
this match {
case Left(_) => b
case Right(a) => Right(a)
}
Please tell me this.
As #jwvh stated in the comment, the you are missing an = in your definition:
def orElse[EE >: E, B](b: => Either[EE, B]): Either[EE, B] = ...
Without the =, the compiler sees an abstract orElse method and some random initialisation code, rather than an implemented method. Sometimes using braces helps get around cases like this
However after adding the = you get type errors because B must be related to A in Either, otherwise this won't match the passed value (similarly to how you got AA), leaving you with:
def orElse[EE >: E, B >: A](b: => Either[EE, B]): Either[EE, B] = ...
Replace the B with AA and you have your original working example
Related
I have been reviewing Scala knowledge and have been going circles about the variance/lower bound.
In the 'functional programming in scala' book, the Either type, it has below signature/exercise (Implement versions of flatMap, orElse on Either that operate on the Right value).
sealed trait Either[+E,+A] {
def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = ???
def orElse[EE >: E, B >: A](b: => Either[EE, B]): Either[EE, B] = ???
}
and the note of the book says
when mapping over the right side, we must promote the left type parameter to some supertype, to satisfy the +E variance annotation. similarly for 'orElse'
My question are:
why do not we have to say B >: A in the flatMap function? we do not need to satisfy +A?
why does orElse signature requires B >: A?
I understand method parameters count as contravariant positions, so we could not possibly have A or E in the method's parameter. i.e. the 'return type' of the f or b could not have E or A in it.
Maybe I am missing something, in relation to the fundamental knowledge of subtyping/lower bound/function as parameter.
Please help me to understand it with maybe some concrete examples.
p.s. Most articles, about variance or upper/lower bound, I found have only 1 type parameter in the class/trait.
why do not we have to say B >: A in the flatMap function? we do not
need to satisfy +A?
flatMap does not put any constraint on the type produced by f: A => Either[EE, B]. This means, for example, that we can have a Either[Throwable, String] and use flatMap to convert it to an Either[Throwable, Int]. Note that the only relationship between String and Int is through Any.
why does orElse signature requires B >: A
When we say: "Give me the left hand side, or else give me the right hand side" we usually want both types to align such that our "fallback", via orElse, will provide a meaningful fallback.
For example, let's use the above example and say we want to take an Either[Throwable, String] and convert it to a Either[Throwable, Int] using flatMap:
val either: Either[Throwable, String] = Right("42")
val res: Either[Throwable, Int] = either.flatMap(str => Try(str.toInt).toEither)
This will work when our String is 42, but if it's not a valid Int, we'll get a Left[Throwable] back. Now let's decide that in case parsing fails, we always want to return -1 as a default value (of course there are better ways to model this, but stick with me). We can leverage orElse for this:
val either: Either[Throwable, String] = Right("42")
val res: Either[Throwable, Int] = either.flatMap(str => Try(str.toInt).toEither).orElse(Right(-1))
This way, the relationship between the LHS and the RHS is preserved, and we receive a sensible value as the result. If B was not constrained to A at all, we would usually get a supertype far up in the type hierarchy, such as AnyRef or Any.
One additional thing about the EE >: E constraint. Since E is covariant, if we tried to use it as a type parameter for the flatMap function:
sealed trait Either[+E, +A] {
def flatMap[B](f: A => Either[E, B]): Either[E, B] = ???
}
The compiler would yell at us:
Error:(7, 20) covariant type E occurs in contravariant position in
type A => Either[E,B] of value f
def flatMap[B](f: A => Either[E, B]): Either[E, B] = ???
That's because covariant types cannot "go in" to the method, they can only be used in the return type, contrary to contravariant type parameters which "go in", but cannot be used in the result type.
If Either were invariant, signatures would be
sealed trait Either[E,A] {
def flatMap[B](f: A => Either[E, B]): Either[E, B] = ???
def orElse(b: => Either[E, A]): Either[E, A] = ???
}
There is no connection between A and B here.
Now if we make Either covariant with respect to E we have to add EE >: E
sealed trait Either[+E,A] {
def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = ???
def orElse[EE >: E](b: => Either[EE, A]): Either[EE, A] = ???
}
Otherwise if we make Either covariant with respect to A we have to add AA >: A
sealed trait Either[E,+A] {
def flatMap[B](f: A => Either[E, B]): Either[E, B] = ???
def orElse[AA >: A](b: => Either[E, AA]): Either[E, AA] = ???
}
Just AA is denoted as B.
In actual case Either is covariant with respect to both type parameters so this is the combination of above.
I guess now it's clear that B in flatMap and B in orElse are different.
I'm reading some articles about ScalaZ and have a question about understanding it. In this article, we generalize the sum function, to abstract away the type to be summed.
def sum[T](xs: List[T])(implicit m: Monoid[T]) = //...
Where
trait Monoid[A] is defined as follows:
trait Monoid[A] {
def mappend(a1: A, a2: A): A
def mzero: A
}
Yes, this is pretty clear. Monoid here corresponds to the algebraic monoid structure. Now in this article it abstracts over lists. To do this we define the following trait:
trait FoldLeft[F[_]] {
def foldLeft[A, B](xs: F[A], b: B, f: (B, A) => B): B
}
object FoldLeft {
implicit val FoldLeftList: FoldLeft[List] = new FoldLeft[List] {
def foldLeft[A, B](xs: List[A], b: B, f: (B, A) => B) = xs.foldLeft(b)(f)
}
}
So now we can define the sum function as follows:
def sum[M[_]: FoldLeft, A: Monoid](xs: M[A]): A = {
val m = implicitly[Monoid[A]]
val fl = implicitly[FoldLeft[M]]
fl.foldLeft(xs, m.mzero, m.mappend)
}
I'm not a theory category expert, but it looks like Applicative functor to me. Is that correct? Can we provide such similarity to category theory.
Applicative functor is a type class with two operations: A => F[A] and F[A => B] => F[A] => F[B]. None of operations you mentioned has such signature. FoldLeft is more like Foldable. It's a different type class, a parent of Traversable (aka Traverse). And Traversable is connected with Applicative.
trait Functor[F[_]] {
def fmap[A, B](f: A => B)(fa: F[A]): F[B]
}
trait Applicative[F[_]] extends Functor[F] {
def pure[A](a: A): F[A]
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
override def fmap[A, B](f: A => B)(fa: F[A]): F[B] = ap(pure(f))(fa)
}
trait Foldable[T[_]] {
def foldr[A, B](op: A => B => B)(seed: B)(ta: T[A]): B =
(foldMap(op)(ta) _)(seed)
def foldMap[A, M](f: A => M)(ta: T[A])(implicit monoid: Monoid[M]): M =
foldr[A, M](a => m => monoid.append(f(a), m))(monoid.empty)(ta)
}
trait Traversable[T[_]] extends Functor[T] with Foldable[T] {
def traverse[F[_]: Applicative, A, B](k: A => F[B])(ta: T[A]): F[T[B]] =
sequence[F, B](fmap[A, F[B]](k)(ta))
def sequence[F[_]: Applicative, A](tfa: T[F[A]]): F[T[A]] =
traverse[F, F[A], A](fa => fa)(tfa)
override def fmap[A, B](f: A => B)(ta: T[A]): T[B] = traverse[Id, A, B](f)(ta)
override def foldr[A, B](op: A => B => B)(seed: B)(ta: T[A]): B =
(traverse[Const[B => B]#λ, A, B](op)(ta) _)(seed)
override def foldMap[A, M: Monoid](f: A => M)(ta: T[A]): M =
traverse[Const[M]#λ, A, M](f)(ta)
}
type Id[A] = A
trait Const[C] {
type λ[A] = C
}
Type class Functor means you have a "container" F[_] and you know how to apply function f: A => B inside this container. Type class Applicative means you know how to pack a value a: A inside this container and how to apply a "function" F[A => B] to a "value" F[A]. Foldable means you know how to fold your container using binary operation A => B => B and starting value B and receiving result B. Traversable means you know how to traverse your container executing applicative effect A => F[B] in every "node".
I have following trait:
sealed trait Sum[+A, +B]
final case class Failure[A](value: A) extends Sum[A, Nothing]
final case class Success[B](value: B) extends Sum[Nothing, B]
object Sum {
def flatMap[AA >: A, B, C](s: Sum[AA, B], f: B => Sum[AA, C]): Sum[AA, C] =
s match {
case Failure(v) => Failure(v)
case Success(v) => f(v)
}
def fold[A, B, C](s: Sum[A, B], success: A => C, failure: B => C): C =
s match {
case Failure(v) => failure(v)
case Success(v) => success(v)
}
def map[A, B, C](s: Sum[A, B], success: A => C): Sum[A,C] =
fold(s, succ => Success(success(succ)), fail => Failure(fail))
}
and the compiler complain:
Cannot resolve symbol A
by:
flatMap[AA >: A, B, C]
What am I doing wrong?
What am I doing wrong?
You're defining a lower bound for a type parameter that doesn't exist. What is A in this context? You're telling the compiler "I want AA to have a lower bound of type A", but the compiler doesn't know a generic type parameter A because it wasn't declared.
If you want to have two type parameter, where one is a lower bound on another (or more generally any bound), it needs to be declared first:
def flatMap[A, AA >: A, B, C]
I have two methods declared in object List:
def reduce[A, B >: A](l: List[A])(f: (B, B) => B): B =
reduceLeft(l)(f)
def reduceLeft[A, B >: A](l: List[A])(op: (B, A) => B): B = ???
I've borrowed the signatures from the scala.collection.TraversableOnce class. (I'm re-creating my own classes for pedagogical reasons).
The compiler is giving me this error:
[error] /.../List.scala:159: type mismatch;
[error] found : (B, B) => B
[error] required: (A, A) => A
[error] reduceLeft(l)(f)
[error] ^
my List class is
final case class ::[A](head: A, tail: List[A]) extends List[A] {
override def isEmpty: Boolean = false
}
sealed trait List[+A] {
def head: A
def tail: List[A]
def isEmpty: Boolean
def ::[B >: A](x: B): List[B] =
datastructures.::(x, this)
}
how can the TraversableOnce definition get away with this? Is it something to do with me not having defined the method as an infix operation?
sealed trait List[+A] {
...
def reduce(op: (B,B) => B):B = reduceLeft(op) // e.g.
... //reduceLeft...
}
Update
I've tried it in the way the TraversableOnce class declares in infix to the trait and it seems to work (see below), however I'm still curious as to why the object definition doesn't work.
sealed trait List[+A] {
...
def reduce[B >: A](f: (B, B) => B): B =
self.reduceLeft(f)
def reduceLeft[B >: A](op: (B, A) => B): B = {
if (isEmpty)
throw new UnsupportedOperationException("empty.reduceLeft")
var first = true
var acc: B = 0.asInstanceOf[B]
for (x <- self) {
if (first) {
acc = x
first = false
}
else acc = op(acc, x)
}
acc
}
#tailrec
final def foreach[U](f: A => U): Unit = { f(head); tail.foreach(f); }
...
}
Here's what's happening here: when you call reduceLeft(l)(f), you first call reduceLeft(l), with l: List[A]. Since your reduceLeft takes two type arguments, but you're not specifying them explicitly, and so far it only has A to work with. So, compiler infers the types as best as it can, using A for both types. You can see this by extracting this partial application into variable:
val partiallyApplied: ((A, A) => A) => A = reduceLeft(l)
Then, when you apply the second argument list with f: (B, B) => B, the types mismatch.
The fix is simple - explicitly state the types when calling reduceLeft:
def reduce[A, B >: A](l: List[A])(f: (B, B) => B): B = {
reduceLeft[A, B](l)(f)
}
the code in question
trait Functor[F[_]] {
def map[A, B](f: A => B): F[A] => F[B]
}
sealed abstract class Free[F[_], A]
case class Return[F[_], A](x: A) extends Free[F, A]
case class Suspend[F[_], A](x: F[Free[F, A]]) extends Free[F, A]
case class Bind[F[_], A, B](x: () => Free[F, A], f: A => Free[F, B]) extends Free[F, B]
// this is the problem child
def liftF[F[_], A](x: => F[A])(implicit F: Functor[F]) =
Suspend[F, A](F.map { Return[F, A] }(x))
Now for some reason in the eclipse scala ide I'm getting a error with liftF
this error
- type mismatch; found : F[core01.Free.Return[F,A]] required: F[core01.Free[F,A]] Note: core01.Free.Return[F,A] <: core01.Free[F,A], but type
F is invariant in type _. You may wish to define _ as +_ instead. (SLS 4.5)
Now in the scalaz source there's no variance annotation, so what's the deal here? why the need for the variance annotation? is there are really need or is there a way around it?
I think you just have the functor arguments backwards on F.map. Give this a try:
def liftF[F[_], A](x: => F[A])(implicit F: Functor[F]) =
Suspend[F, A](F.map(x) { Return[F, A] } )
println(liftF(List(1, 2)))
"Suspend(List(Return(1), Return(2)))"
Note that either way you apply the elements to functor map you'll get the same result (you're version and the scalaz version):
def mapReverse[F[_], A, B](implicit F: Functor[F]) = {
(f:A => B) => (fa:F[A]) => F.map(fa)(f)
}
val reverse = mapReverse(F)({
Return[F, A]
})(x)
val normal = F.map(x)({
Return[F, A]
})
In this case reverse and normal are both F[Return[F, A]]. The way you apply the parameters is only helpful in the context of Suspend:
Suspend[F, A](F.map(x)({ Return[F, A] })) // works great
Suspend[F, A](normal) // fails to compile
Suspend[F, A](reverse) // fails to compile
I'm sure if you dig through the scala language spec long enough you can find out why the type inference works this way. If I had to guess, when fa is applied to F.map you get a function that's of type (A => B) => F[B]. So the compiler probably looks and sees that Suspend takes a Free so it makes this (A => Free[F, A]) => F[Free[F, A]] which will gladly take Return[F, A].apply as an argument. When you apply the arguments the other way you're strongly typed to Return instead of inferring a function Free.