1

I tried to write simple implementation of flatMap for Either

sealed trait Either[+L, +R] {
  def flatMap[B](f: R => Either[L, B]): Either[L, B] = this match {
    case Left(e) => Left(e)
    case Right(e) => f(e)
  }
}

final case class Right[+A, +B](right: B) extends Either[A, B]
final case class Left[+A, +B](left: A) extends Either[A, B]

and faced following problem: covariant type L is in contravariant position in type f: R => Either[L, B] of value f, but why is it so? I thought that our type is in contravariant position when we take variant type as an argument for function and it has nothing to do with a type declaration

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
nick sigevsky
  • 37
  • 1
  • 5

1 Answers1

3

You can think of R => Either[L, B] as a "generalized value of type L" - it's not exactly the same thing as an L, but given an R it might produce an L. So, your flatMap "consumes generalized values of type L". At the same time, your variance declaration claims that Either[+L, +R] is covariant in L, therefore, an Either[VerySpecial, R] would have to be a special case of Either[RatherGeneral, R]. But this is impossible, because the flatMap that can consume only VerySpecial values would choke on a RatherGeneral input.

  • In Either[+L, +R], L is in covariant position (it "produces" Ls, at least sometimes)
  • In R => Either[L, B], L is still in covariant position (because the function produces Either[L, B], and Either[L, B] in turn produces Ls, so the whole thing produces Ls)
  • In (R => Either[L, B]) => Either[L, B], the first L appears in contravariant position, because the argument part is consumed by the method flatMap.

This is easily fixed with the standard lower-type-bounds trick:

sealed trait Either[+L, +R] {
  def flatMap[B, M >: L](f: R => Either[M, B]): Either[M, B] = this match {
    case Left(e) => Left(e)
    case Right(e) => f(e)
  }
}

final case class Right[+A, +B](right: B) extends Either[A, B]
final case class Left[+A, +B](left: A) extends Either[A, B]
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • Thanks, great explanation! But then why type R is not highlighted in R => Either[L, B] with an error as its in contravariant position ( f consumes R which in turn consumed by flatMap) – nick sigevsky Aug 27 '18 at 13:50
  • 1
    @nicksigevsky Because `(R => X) => Y` should be thought of as a generalized value of type `R` [which is, indeed, a very general principle](https://en.wikipedia.org/wiki/Yoneda_lemma#The_Yoneda_embedding). `R` is covariant in `(R => X) => Y`. – Andrey Tyukin Aug 27 '18 at 14:03
  • 1
    @nicksigevsky Suppose you say "I need a printer `P`". Then I ask you: what do you need the printer for? You answer: "I have special software and a PDF file that tells me how, given a printer `P`, I can generate a paper artifact `A`" (that is, you have an `P => A`). Then I say to you: "just go to a copyshop, it will take your `P => A`, and print a whole `List[A]`, if you want". That is, a copyshop is of type `(P => A) => List[A]`. Now, notice that you can get what you want (printed artifact `A`), because a copyshop `(P => A) => List[A]` behaves like a *generalized printer* `P`. – Andrey Tyukin Aug 27 '18 at 14:04