Fabian Schmitthenner
Fabian Schmitthenner

Reputation: 1706

Scala Type classes and don't work well with inheritence

I currently program some classes which implement type classes. I use cats for this purpose. In this setting, I have classes A[T] and B[T] <: A[T]. A[T] is a monad, so when importing the right libraries, I can write

val x: A[Int] = ???
x.flatMap(_)

but I can't write

val x: B[Int] = ???
b.flatMap(_)

as scala doesn't find the flatMap operator. Instead, I have to write

val x: B[Int] = ???
(b: A[Int]).flatMap(_)

for this to work. This is awkward as I expect subclasses to inherit the methods of superclasses.

For completeness, I add a minimal example without dependencies which shows the issue. In this, Filterable is a typeclass, that is implemented by S.

object StackOverflowTest {
  class S[+T]

  class Q[+T] extends S[T]

  trait Filterable[F[+_]] {
    def filter[A](x: F[A]): F[A]
  }

  implicit class FilterOps[F[+_], A](y: F[A])(implicit ev: Filterable[F]) {
    def filter: F[A] = ev.filter(y)
  }

  implicit object SIsFilterable extends Filterable[S] {
    def filter[A](x: S[A]): S[A] = x
  }

  //def failing = {
  //  val q = new Q[Int]()
  //  val r = q.filter
  //}

  def working = {
    val q = new Q[Int]()
    val r = (q: S[Int]).filter
  }
}

The failing definition doesn't compile, because q.op isn't found. On the other hand, working mitigates this issue by first casting to S. What can I do to make the failing example work for users of my library? It feels very unnatural that you have to cast to a parent type to be able to use a method of it.

Note, that I can't define a second implicit object which implements Filterable[Q] because (new Q[Int]()).filter should be of type S[Int] and not Q[Int].

Upvotes: 1

Views: 98

Answers (1)

Alvaro Carrasco
Alvaro Carrasco

Reputation: 6182

Try replacing SIsFilterable with:

implicit def sOrSubtypeIsFilterable [T[+_] <: S[_]] = new Filterable[T] {
  def filter[A](x: T[A]): T[A] = x
}

Upvotes: 1

Related Questions