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 {}