Search code examples
scalagenericstypeclass

Scala type class to extend generic type: No implicits found for parameter


I want to write a type class, to add some behavior to a generic type. However, I cannot figure out how to do it; I keep running into the error below.

Imagine you have a generic type MyList[A]:

trait MyList[A]

object MyList {
  case class Empty[A]() extends MyList[A]
  case class Node[A](value: A, next: MyList[A]) extends MyList[A]
}

Now you want to add some behavior to this class, e.g. to convert it into a Stream[A]. A type class based extension would seem appropriate:

// inspired by https://scalac.io/typeclasses-in-scala

trait MyListExt[T, A <: MyList[T]] {
  def stream(a: A): Stream[T]
}

object MyListExt {
  def apply[T, A <: MyList[T]](implicit a: MyListExt[T, A]): MyListExt[T, A] = a

  object ops {
    implicit class MyListExtOps[T, A <: MyList[T]](val a: A) extends AnyVal {
      def stream(implicit ext: MyListExt[T, A]): Stream[T] = ext.stream(a)
    }
  }

  private type T0

  implicit val myListToStreamImpl: MyListExt[T0, MyList[T0]] = new MyListExt[T0, MyList[T0]] {
    override def stream(a: MyList[T0]): Stream[T0] = {
      def fold[T1](l: MyList[T1], acc: Stream[T1]): Stream[T1] = l match {
        case MyList.Empty() => acc
        case MyList.Node(value, next) => fold(next, acc :+ value)
      }
      fold(a, Stream.empty)
    }
  }
}

When I now try to use this type class in my code, I get the following error at l.stream:

No implicits found for parameter ext: MyListExt[T_, MyList[Int]]
object MyListTest {
  def main(args: Array[String]): Unit = {
    import MyListExt.ops._
    val l: MyList[Int] = MyList.Node(1, MyList.Node(2, MyList.Node(3, MyList.Empty())))
    l.stream.foreach(println)
  }
}

What am I doing wrong, or how can I get my l.stream to work?
I have seen many examples involving type classes and implicit conversion, but none so far operating on generic types.


Solution

  •   implicit def myListToStreamImpl[T]: MyListExt[T, MyList[T]] = new MyListExt[T, MyList[T]] {
        override def stream(a: MyList[T]): Stream[T] = {
          def fold(l: MyList[T1], acc: Stream[T1]): Stream[T1] = l match {
            case MyList.Empty() => acc
            case MyList.Node(value, next) => fold(next, acc :+ value)
          }
          fold(a, Stream.empty[T1])
        }
      }
    

    Your types don't align because you've used that private type for whatever bizarre reason. Types nested inside objects have a completely different application, and they don't relate to your current use case.