FAQs

Does cats-tagless support algebras with extra type parameters?

Yes. e.g.

import cats.tagless._
import cats.~>
import util.Try

@autoFunctorK @finalAlg
trait Foo[F[_], T] {
  def a(i: Int): F[T]
}

implicit val tryFoo: Foo[Try, String] = i => Try(i.toString)
implicit val fk: Try ~> Option = λ[Try ~> Option](_.toOption)
import Foo.autoDerive._
Foo[Option, String].a(3)
// res0: Option[String] = Some(value = "3")

Does Cats-tagless support algebras with abstract type member?

Yes but with some caveats. The FunctorK instance it generates does not refine to the type member. E.g.

@autoFunctorK @finalAlg
trait Bar[F[_]] {
  type T
  def a(i: Int): F[T]
}

object Bar {
  type Aux[F[_], A] = Bar[F] { type T = A }
}

implicit val tryBarString: Bar.Aux[Try, String] = new Bar[Try] {
   type T = String
   def a(i: Int): Try[String] = Try(i.toString)
}

If you try to map this tryBarString to a Bar[Option], the type T of the Bar[Option] isn’t refined. That is, you can do

import Bar.autoDerive._
Bar[Option].a(3)
// res1: Option[Bar[Option]#T] = Some(value = "3")

But you can’t create a Bar[Option] { type T = String } from the tryBarString using FunctorK.

val barOption: Bar[Option] { type T = String } = tryBarString.mapK(fk)
// error: value mapK is not a member of repl.MdocSession.MdocApp.Bar.Aux[scala.util.Try,String]
// val barOption: Bar[Option] { type T = String } = tryBarString.mapK(fk)
//                                                  ^^^^^^^^^^^^^^^^^

However, there is also mapK function added to the companion object of the algebra which gives you more precise type.

val barOption: Bar[Option] { type T = String } = Bar.mapK(tryBarString)(fk)
// barOption: Bar[Option]{type T = String} = repl.MdocSession$MdocApp$Bar$$anon$6$$anon$7@b4b7863

Also since the FunctorK (or InvariantK) instance uses a dependent type on the original interpreter, you may run into dependent type related issues. In those cases, this mapK (or imapK) on the companion object may give better result. Here are two examples.

Cannot resolve implicit defined by the dependent type

import cats.Show
import cats.implicits._

@autoFunctorK
trait Algebra[F[_]] {
  type TC[T]
  def a[T: TC](t: T): F[String]
}

object tryAlgInt extends Algebra[Try] {
  type TC[T] = Show[T]
  def a[T: TC](t: T): Try[String] = Try(t.show)
}

FunctorK.mapK will result in unusable interpreter due to scalac’s difficulty in resolving implicit based on dependent type.

FunctorK[Algebra].mapK(tryAlgInt)(fk).a(List(1,2,3))
// error: could not find implicit value for evidence parameter of type stabilizer$1.TC[List[Int]]
// FunctorK[Algebra].mapK(tryAlgInt)(fk).a(List(1,2,3))
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The mapK on the companion will work fine.

Algebra.mapK(tryAlgInt)(fk).a(List(1,2,3))
// res4: Option[String] = Some(value = "List(1, 2, 3)")

Cannot take in argument whose type is a dependent type

@autoInvariantK
trait InvAlg[F[_]] {
  type T
  def a(i: F[T]): F[T]
}

object tryInvAlgInt extends InvAlg[Try] {
  type T = String
  def a(i: Try[String]): Try[String] = i.map(_ + "a")
}
implicit val rk: Option ~> Try = λ[Option ~> Try](o => Try(o.get))

InvariantK.imapK will result in unusable interpreter because method a’s argument type is a dependent type on original interpreter.

InvariantK[InvAlg].imapK(tryInvAlgInt)(fk)(rk).a(Some("4"))
// error: type mismatch;
//  found   : String("4")
//  required: stabilizer$2.T
// InvariantK[InvAlg].imapK(tryInvAlgInt)(fk)(rk).a(Some("4"))
//                                                       ^^^

The imapK on the companion will work fine

InvAlg.imapK(tryInvAlgInt)(fk)(rk).a(Some("4"))
// res6: Option[String] = Some(value = "4a")

I am seeing diverging implicit expansion for type MyAlgebra[F]

If you see error likes the following when you try to summon a specific instance of MyAlgebra

diverging implicit expansion for type MyAlgebra[F]

[error] starting with method autoDeriveFromFunctorK in object MyAlgebra

It probably means that necessary implicit MyAlgebra instance and/or the corresponding FunctionK is missing in scope.

I’m seeing java.lang.NoClassDefFoundError at runtime

If you see an error like the following

java.lang.NoClassDefFoundError: com/mypackage/MyAlgebra$

This can be due to multiple annotations on a trait without a companion object. This is a known issue. The solution is to add an empty companion object:

@finalAlg
@autoContravariant
trait MyAlgebra[T] {
  def foo(t: T): String
}

object MyAlgebra {}