0

Here's the code:

// eventually will be an implicit class with extension methods
class EitherTWrapper [L,R] (ei: EitherT[Future,L,R])

new EitherTWrapper(??? : EitherT[Future,Nothing,Boolean])

Fails to compile with:

type mismatch;
 found   : cats.data.EitherT[scala.concurrent.Future,Nothing,Boolean]
 required: cats.data.EitherT[scala.concurrent.Future,L,Boolean]
Note: Nothing <: L, but class EitherT is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)

It works fine if I provide the types explicitly, like this:

new EitherTWrapper[Nothing,Boolean](??? : EitherT[Future,Nothing,Boolean])

Which would work, except that I can't do that if I'm trying to make it an implicit class.

I expected this to work. How do I define a class that can wrap an EitherT?

Alvaro Carrasco
  • 6,103
  • 16
  • 24
  • Does it work if you use a def in a companion object to do the construction, rather than using new directly? – gandaliter Jan 27 '20 at 17:18
  • 3
    As the error suggests, does `class EitherTWrapper [+L,R] (ei: EitherT[Future,L,R])` not work? – James Whiteley Jan 27 '20 at 18:33
  • @JamesWhiteley I think the error is referring to A in EitherT, so I didn't even consider changing it in EitherTWrapper... but you're right, changing it on the wrapper did fix that issue (it also brought on other issues because i had methods that were using A in contravariant position, but was able to figure out a way to get those to work too). Thanks. – Alvaro Carrasco Jan 27 '20 at 23:32

2 Answers2

0

As the error suggests, changing your wrapper to class EitherTWrapper [+L,R] (ei: EitherT[Future,L,R]) will fix your compilation errors.

Your error states that Nothing <: L, but class EitherT is invariant in type A. - this means that Nothing is a subtype of L, so declaring L is illegal since it implies you explicitly want L not its subtypes (i.e. L is invariant).

Declaring something as +L makes it covariant, making what you want possible. Read up more on variance in the scala docs: https://docs.scala-lang.org/tour/variances.html

The reason the below code (from the docs) works is because Scala's List is defined as List[+A], meaning that you can also pass in List[Cat] and List[Dog] to a function that takes a List[Animal]:

abstract class Animal {
  def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal

object CovarianceTest extends App {
  def printAnimalNames(animals: List[Animal]): Unit = {
    animals.foreach { animal =>
      println(animal.name)
    }
  }

  val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom"))
  val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))

  printAnimalNames(cats)
  // Whiskers
  // Tom

  printAnimalNames(dogs)
  // Fido
  // Rex
}

On Scastie

James Whiteley
  • 3,363
  • 1
  • 19
  • 46
  • My problem is not really variance, but inference. When the types are provided explicitly, the original code compiles correctly. Changing the variance is a workaround for an inference problem. I think the problem has more to do with the `Nothing` type and how it's treated specially by the compiler. Will have to return to this later with more info. – Alvaro Carrasco Jan 28 '20 at 15:13
0

Apparently, it's a known scala compiler bug (limitation?): https://github.com/scala/bug/issues/9453

It seems there's 2 workarounds:

  • Make the type covariant on the wrapper (not great because now the wrapper has a different variance behavior than the thing being wrapped and only works if the type param is not being used contravariantly).
  • Treat the version of EitherT with Nothing specially, by creating a separate wrapper for it. This works even when trying to use the wrappers as implicit classes.
Alvaro Carrasco
  • 6,103
  • 16
  • 24