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.
Related
this is a direct translation of my scala2 code to scala3
trait Narrow[F[_], A, B <: A: ClassTag]:
def apply(fa: F[A]): F[B]
extension [F[_], A] (fa: F[A]):
def narrow[B: ClassTag] (using op: Narrow[F, A, B]): F[B] = op(fa)
I need to specify the type of the narrow operation at the call site, however extension methods do not allow that syntax. what are there the best workarounds for this limitation?
the purpose of this is to be able to narrow the type in a collection / try / whatever. the narrow type class will flatmap whatever is inside, compare the runtime type, if it matches wrap that B in an F, or otherwise return an empty F
trait A
trait B extends A
object A extends A
object B extends B
val bb: List[B] = List(A, A, B, B, A)
.narrow[B]
assert(bb == List(B, B))
You could use a polymorphic function, if you can handle the ugliness:
extension [F[_], A] (fa: F[A]):
def narrow() = [B <: A] => (using op: Narrow[F, A, B]) => op(fa)
You can then call it with foo.narrow()[String]. Here it is in Scastie.
The narrow() is necessary, because without it, the type argument would go to the extension and not the polymorphic function.
In the future, Scala 3 may allow type arguments directly to methods inside extensions, but right now, you can keep using your Scala 2 implicit class and change it after the next release:
implicit class NarrowOps[F[_], A](fa: F[A]):
def narrow[B <: A](using op: Narrow[F, A, B]) = op(fa)
Scastie
Side note: You don't need B: ClassTag again in your extension, although I believe you do need to use the bound B <: A.
I wasnt able to live with the () on the call site. I decided trying a implicit conversion to a type with an apply method with just a type parameter.
trait NarrowTypeClass[F[_], A, B <: A: ClassTag]:
def apply(fa: F[A]): F[B]
given [F[_], A] as Conversion[F[A], Narrowable[F, A]] = Narrowable(_)
sealed class Narrowable [F[_], A] (fa: F[A]):
def narrow[B <: A: ClassTag] (using op: NarrowTypeClass[F, A, B]): F[B] = op(fa)
this seems to do the trick
I am trying to implement a cats Monad instance for a type that has multiple type parameters. I looked at the cats Either instance to see how it was done there. Part of the Either Monad instance code from cats is copied below:
import cats.Monad
object EitherMonad {
implicit def instance[A]: Monad[Either[A, ?]] =
new Monad[Either[A, ?]] {
def pure[B](b: B): Either[A, B] = Right(b)
def flatMap[B, C](fa: Either[A, B])(f: B => Either[A, C]): Either[A, C] =
fa.right.flatMap(f)
}
}
It fails to compile with the error: error: not found: type ?
What is the ? type and how can I use it when creating instances for my own types?
It is special syntax for so-called type lambdas that is added by the kind projector plugin.
Either[A, ?]
is a shortcut for
({type L[X] = Either[A, X]})#L
The whole code desugars to
import cats.Monad
object EitherMonad {
implicit def instance[A]: Monad[({type L[X] = Either[A, X]})#L] = new Monad[({type L[X] = Either[A, X]})#L] {
def pure[B](b: B): Either[A, B] = Right(b)
def flatMap[B, C](fa: Either[A, B])(f: B => Either[A, C]): Either[A, C] =
fa.right.flatMap(f)
}
}
Type lambdas look frightening, but they are essentially a very simple concept. You have a thing that takes two type parameters, like Either[A, B]. You want to provide a monad instance for Either, but trait Monad[F[_]] takes only one type parameter. But in principle that's OK, since your monad instance is only concerned with the second (the "right") type argument anyway. A type lambda is just a way to "fix" the first type argument so you have the right shape.
If you would do the same thing at value level, you wouldn't even think about it twice. You got a function of two arguments
val f: (Int, Int) => Int = ...
And something you want to pass f to, which only takes 1 argument
def foo(x: Int => Int) = ...
The only way to make things fit is to fix one of the arguments
foo(x => f(1, x))
And that's exactly what a type lambda does at type level.
I have the following trait (simplified example)
trait F[A, M[_] <: Option[A]] {
def v: A
def f: A => M[A]
}
I want to be able to create the following trait
trait G[A] extends F[A, Some]
But this gives the following error
Error:(18, 20) type arguments [A,Some] do not conform to trait F's type parameter bounds [A,M[_] <: Option[A]]
How can I bound the M[_] parameterized type ?
Edit :
A type F[A, M[_] <: Option[_]] will work. But I actually have another function in my trait
trait F[A, M[_] <: Option[_]] {
def v: A
def f: A => M[A]
def f2: A => A = {
(a: A) => f(a).get
}
}
And in that case in f2, get doesn't return a value of type A even if f returns a M[A]
Error:(17, 20) type mismatch;
found : _$1
required: A
(a: A) => f(a).get
You can fully constrain the type of M since it doesn't really need to take type parameters:
trait F[A, M <: Option[A]] { // M no longer takes a type parameter
def v: A
def f: A => M
}
trait G[A] extends F[A, Some[A]] // Must specify M[A] since M doesn't take a type parameter
However you may want to provide some sort of mapping mechanism or something and go to some other type M[B] in which case a less constrained version would work better:
trait F[A, M[_] <: Option[_]] { // M is no longer constrained to A
def v: A
def f: A => M[A] // M is manually constrained to A here
}
trait G[A] extends F[A, Some]
UPDATE
Unfortunately your f2 function won't work. Even though we know that the types are correct, the compiler can't properly infer the type (limitations of Scala). You could add this function to G[A] and it will work as expected because M[_] has a concrete type with it now.
trait G[A] extends F[A, Some]{
def f3: A => A = a => f(a).get
}
And as a side note, you should never use Option.get, it defeats the purpose of using an Option! flatMap it or something :)
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.
could anyone help me with below code bits from the book?
trait Mapper[F[_]] {
def fmap[A, B](xs: F[A], f: A => B): F[B]
}
def VectorMapper = new Mapper[Vector] {
def fmap[A, B](xs: Vector[A], f: A => B): Vector[B] = xs map f
}
That was simple: trait definition using higher-kinded type F[_] for usage with any "container-like" types, then a concrete mapper for Vector.
Then goes a tricky part. Mapper for Either.
I understand {type E[A] = Either[X, A]} just as a block of code, and ({type E[A] = Either[X, A]})#E as a projection that takes that type alias E out of the anonymous block of code and by that author "hides" the presence of X for the Mapper trait because trait operates on single type parameter "container types" only - and we are interested in A, i.e. Right.
def EitherMapper[X] = new Mapper[({type E[A] = Either[X, A]})#E ] {
def fmap[A, B](r: Either[X, A], f: A => B): Either[X, B] = r match {
case Left(a) => Left(a)
case Right(a) => Right(f(a))
}
}
Question:
Why do we need X in the def EitherMapper[X] = part?
Thanks for details.
Either is dependent on two types, for instance Either[Int, String]
EitherMapper is a type constructor that is dependent just on one type, so when you have a EitherMapper[Int], you are dealing with a Either[Int, A], and A is resolved into the Mapper part, this way you can have any A=>B function, because the first type of Either is already present for the Mapper and you return a Either[X, B].
Indeed the type E[A] is equivalent to Either[X, A], you have just one degree of freedom regarding to types!
val right: Either[Boolean, String] = Right("test")
val left: Either[Boolean, String] = Left(false)
println(EitherMapper.fmap(right, (s: String) => s.length))
> Right(4)
println(EitherMapper.fmap(left, (s: String) => s.length))
> Left(false)
In this case the type is EitherMapper[Boolean] and the type of fmap is fmap[String, Integer], it accepts Either[Boolean, String] and return Either[Boolean, Integer].
As you can see the type of fmap doesn't say anything on the X part of the Either[X, A] type so in the end you could use the (s: String) => s.length) function for others EitherMapper[X] types, in simple words the "left" part of the either type can be anything you want, and it's the "X" part of the type construction.
Hope it's clearer now!