Mainecoon is a small library built to facilitate transforming and composing tagless final encoded algebras.
Mainecoon is available on scala 2.11, 2.12, and scalajs. The macro annotations are developed using scalameta, so there are a few dependencies to add in your build.sbt
.
addCompilerPlugin(
("org.scalameta" % "paradise" % "3.0.0-M9").cross(CrossVersion.full))
libraryDependencies +=
"com.kailuowang" %% "mainecoon-macros" % latestVersion //latest version indicated in the badge above
Note that org.scalameta.paradise
is a fork of org.scalamacros.paradise
. So if you already have the
org.scalamacros.paradise
dependency, you might need to replace it.
Say we have a typical tagless encoded algebra ExpressionAlg[F[_]]
import mainecoon._
@autoFunctorK
trait ExpressionAlg[F[_]] {
def num(i: String): F[Float]
def divide(dividend: Float, divisor: Float): F[Float]
}
With Mainecoon you can transform this interpreter using Cats' FunctionK
. I.e, you can transform an ExpressionAlg[F]
to a ExpressionAlg[G]
using a FunctionK[F, G]
, a.k.a. F ~> G
.
For example, if you have an interpreter of ExpressionAlg[Try]
import util.Try
object tryExpression extends ExpressionAlg[Try] {
def num(i: String) = Try(i.toFloat)
def divide(dividend: Float, divisor: Float) = Try(dividend / divisor)
}
You can transform it to an interpreter of ExpressionAlg[Option]
import mainecoon.implicits._
import cats.implicits._
import cats._
val fk : Try ~> Option = λ[Try ~> Option](_.toOption)
tryExpression.mapK(fk)
// res0: ExpressionAlg[Option]
Note that the Try ~> Option
is implemented using kind projector's polymorphic lambda syntax.
Obviously FunctorK
instance is only possible when the effect type F[_]
appears only in the
covariant position (i.e. the return types). For algebras with effect type also appearing in the contravariant position (i.e. argument types), mainecoon provides a InvariantK
type class and an autoInvariantK
annotation to automatically generate instances.
@autoFunctorK
also add an auto implicit derivation, so that if you have an implicit ExpressionAlg[F]
and an implicit
F ~> G
, you can automatically have a ExpressionAlg[G]
.
It works like this
import ExpressionAlg.autoDerive._
implicictly[ExpressionAlg[Option]] //implicitly derived from a `ExpressionAlg[Try]` and a `Try ~> Option`
This auto derivation can be turned off using an annotation argument: @autoFunctorK(autoDerivation = false)
.
With mainecoon, you can lift your algebra interpreters to use Free
to achieve stack safety.
For example, say you have an interpreter using Try
@finalAlg @autoFunctorK
trait Increment[F[_]] {
def plusOne(i: Int): F[Int]
}
implicit object incTry extends Increment[Try] {
def plusOne(i: Int) = Try(i + 1)
}
def program[F[_]: Monad: Increment](i: Int): F[Int] = for {
j <- Increment[F].plusOne(i)
z <- if (j < 10000) program[F](j) else Monad[F].pure(j)
} yield z
Obviously, this program is not stack safe.
program[Try](0)
//throws java.lang.StackOverflowError
Now lets use auto derivation to lift the interpreter with Try
into an interpreter with Free
import cats.free.Free
import cats.arrow.FunctionK
import Increment.autoDerive._
implicit def toFree[F[_]]: F ~> Free[F, ?] = λ[F ~> Free[F, ?]](t => Free.liftF(t))
program[Free[Try, ?]](0).foldMap(FunctionK.id)
// res9: scala.util.Try[Int] = Success(10000)
Again the magic here is that mainecoon auto derive an Increment[Free[Try, ?]]
when there is an implicit Try ~> Free[Try, ?]
and a Increment[Try]
in scope. This auto derivation can be turned off using an annotation argument: @autoFunctorK(autoDerivation = false)
.
You can use the CartesianK
type class to create a new interpreter that runs both interpreters and return the result as a cats.Tuple2K
. The @autoCartesianK
attribute add an instance of CartesianK
to the companion object. Example:
@autoCartesianK
trait ExpressionAlg[F[_]] {
def num(i: String): F[Float]
def divide(dividend: Float, divisor: Float): F[Float]
}
val prod = tryExpression.productK(optionExpression)
prod.num("2")
// res11: cats.data.Tuple2K[Option,scala.util.Try,Float] = Tuple2K(Some(2.0),Success(2.0))
If you want to combine more than 2 interpreters, the @autoProductNK
attribute add a series of product{n}K (n = 3..9)
methods to the companion object. Unlike productK
living in the CartesianK
type class, currently we don't have a type class for these product{n}K
operations yet.
Mainecoon also provides two annotations that can generate cats.Functor
and cats.Invariant
instance for your trait.
For documentation/FAQ/guides, go to kailuowang.com/mainecoon.
Any contribution is more than welcome. Also feel free to report bugs, request features using github issues or gitter.
Discussion around mainecoon is encouraged in the Gitter channel as well as on Github issue and PR pages.
We adopted the Typelevel Code of Conduct. People are expected to follow it when discussing mainecoon on the Github page, Gitter channel, or other venues.
Copyright (C) 2017 Kailuo Wang http://kailuowang.com
Mainecoon is licensed under the Apache License 2.0