Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better RepresentableK derivation #147

Merged
merged 3 commits into from
Feb 11, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
representable derivation
Oleg Nizhnik committed Feb 10, 2020

Verified

This commit was signed with the committer’s verified signature. The key has expired.
addaleax Anna Henningsen
commit 0e6392f62002d9164393a1562d13f347e2a2060e
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -185,7 +185,7 @@ lazy val derivation =
defaultSettings,
libraryDependencies ++= Seq(magnolia, derevo, catsTagless),
macros,
publishName := "derivation"
publishName := "derivation",
)
.dependsOn(data)

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package tofu.higherKind.derived
import cats.data.{IorT, NonEmptyChain}
import cats.free.Free
import derevo.derive
import tofu.higherKind.Embed
@@ -8,6 +9,7 @@ object EmbedKSuite {
trait Foo[F[_]] {
def foo(x: Int, s: String): F[Double]
def bar(a: List[Int]): Free[F, Unit]
def baz(xx: Double): IorT[F, NonEmptyChain[String], Long]
}

Embed[Foo]
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package tofu.higherKind.derived

import cats.data.Tuple2K
import RepresentableKSuite.Foo
import cats.data.{OptionT, Tuple2K}
import cats.instances.either._
import cats.syntax.either._
import cats.syntax.functor._
import cats.{Id, ~>}
import org.scalatest.{FlatSpec, Matchers}
import tofu.data.Embedded
import RepresentableKSuite.Foo
import tofu.syntax.functionK.funK
import tofu.syntax.embed._
import cats.tagless.syntax.functorK._
import cats.tagless.syntax.semigroupalK._
import cats.tagless.syntax.applyK._
import cats.{Id, ~>}
import derevo.derive
import tofu.higherKind.derived.representableK
import org.scalatest.{FlatSpec, Matchers}
import tofu.data.Embedded
import tofu.higherKind.{RepK, RepresentableK}
import tofu.syntax.embed._
import tofu.syntax.functionK.funK

import scala.util.Try

@@ -25,11 +23,13 @@ class RepresentableKSuite extends FlatSpec with Matchers {
Try(s.toDouble).toEither.left.map(_ => s"could not parse $s as double").map(_ * x)
override def bar(a: List[Int]): Either[String, Unit] =
a.headOption.toRight("must contain at least one element").void
def baz(a: List[Int]): OptionT[Either[String, *], Unit] = OptionT.liftF(Left("hello"))
}

val defaultFoo: Foo[Id] = new Foo[Id] {
override def foo(x: Int, s: String): Double = x.toDouble
override def bar(a: List[Int]): Unit = ()
def baz(a: List[Int]): OptionT[Id, Unit] = OptionT.none
}

"representableK" should "generate nice mapK" in {
@@ -82,6 +82,28 @@ object RepresentableKSuite {
@derive(representableK)
trait Foo[F[_]] {
def foo(x: Int, s: String): F[Double]

def bar(a: List[Int]): F[Unit]

def baz(a: List[Int]): OptionT[F, Unit]
}

trait Foo1[F[_]] {
def foo(x: Int, s: String): F[Double]

def bar(a: List[Int]): F[Unit]

def baz(a: List[Int]): OptionT[F, Unit]
}

new RepresentableK[Foo1] {
def tabulate[F[_]](hom: RepK[Foo1, *] ~> F): Foo1[F] = new Foo1[F] {
def foo(x: Int, s: String): F[Double] = hom(RepK.mk(_.foo(x, s)))
def bar(a: List[Int]): F[Unit] = hom(RepK.mk(_.bar(a)))
def baz(a: List[Int]): OptionT[F, Unit] = {
val OT = RepresentableK[OptionT[*[_], Unit]]
OT.tabulate[F](funK(rep => hom(RepK[Foo1](foo => rep(foo.baz(a))))))
}
}
}
}
30 changes: 2 additions & 28 deletions higherKindCore/src/main/scala/tofu/higherKind/Embed.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package tofu.higherKind

import cats.FlatMap
import cats.data.{EitherT, IorT, OptionT, ReaderT, WriterT}
import cats.free.Free
import simulacrum.typeclass
import tofu.syntax.monadic._

@typeclass trait Embed[U[_[_]]] {
def embed[F[_]: FlatMap](ft: F[U[F]]): U[F]
@@ -19,37 +17,13 @@ trait EmbedInstanceChain[TC[u[_[_]]] >: Embed[u]] extends RepresentableKInstance
def embed[F[_]: FlatMap](ft: F[Free[F, A]]): Free[F, A] = Free.roll(ft)
}

private[this] def optionTInstance[A]: Embed[OptionT[*[_], A]] = new Embed[OptionT[*[_], A]] {
def embed[F[_]: FlatMap](ft: F[OptionT[F, A]]): OptionT[F, A] = OptionT(ft.flatMap(_.value))
}

private[this] def eitherTInstance[E, A]: Embed[EitherT[*[_], E, A]] = new Embed[EitherT[*[_], E, A]] {
def embed[F[_]: FlatMap](ft: F[EitherT[F, E, A]]): EitherT[F, E, A] = EitherT(ft.flatMap(_.value))
}

private[this] def writerTInstance[W, A]: Embed[WriterT[*[_], W, A]] = new Embed[WriterT[*[_], W, A]] {
def embed[F[_]: FlatMap](ft: F[WriterT[F, W, A]]): WriterT[F, W, A] = WriterT(ft.flatMap(_.run))
}

private[this] def iorTInstance[E, A]: Embed[IorT[*[_], E, A]] = new Embed[IorT[*[_], E, A]] {
def embed[F[_]: FlatMap](ft: F[IorT[F, E, A]]): IorT[F, E, A] = IorT(ft.flatMap(_.value))
}
private[this] val freeEmbedAny = freeInstance[Any]

private[this] def readerTInstance[R, A]: Embed[ReaderT[*[_], R, A]] = new Embed[ReaderT[*[_], R, A]] {
def embed[F[_]: FlatMap](ft: F[ReaderT[F, R, A]]): ReaderT[F, R, A] = ReaderT(r => ft.flatMap(_.run(r)))
}

private[this] val freeEmbedAny = freeInstance[Any]
private[this] val optionTEmbedAny = optionTInstance[Any]
private[this] val eitherTEmbedAny = eitherTInstance[Any, Any]
private[this] val writerTEmbedAny = writerTInstance[Any, Any]
private[this] val iorTEmbedAny = iorTInstance[Any, Any]
private[this] val readerTEmbedAny = readerTInstance[Any, Any]

final implicit def freeEmbed[A]: TC[Free[*[_], A]] = freeEmbedAny.asInstanceOf[TC[Free[*[_], A]]]
final implicit def optionEmbed[A]: TC[OptionT[*[_], A]] = optionTEmbedAny.asInstanceOf[TC[OptionT[*[_], A]]]
final implicit def eitherTEmbed[E, A]: TC[EitherT[*[_], E, A]] = eitherTEmbedAny.asInstanceOf[TC[EitherT[*[_], E, A]]]
final implicit def writerTEmbed[W, A]: TC[WriterT[*[_], W, A]] = writerTEmbedAny.asInstanceOf[TC[WriterT[*[_], W, A]]]
final implicit def iorTEmbed[E, A]: TC[IorT[*[_], E, A]] = iorTEmbedAny.asInstanceOf[TC[IorT[*[_], E, A]]]
final implicit def readerTEmbed[R, A]: TC[ReaderT[*[_], R, A]] = readerTEmbedAny.asInstanceOf[TC[ReaderT[*[_], R, A]]]

}
101 changes: 99 additions & 2 deletions higherKindCore/src/main/scala/tofu/higherKind/RepresentableK.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package tofu.higherKind
import cats.data.Tuple2K
import cats.data.{EitherT, IorT, OptionT, ReaderT, Tuple2K, WriterT}
import cats.free.Cofree
import cats.tagless.IdK
import cats.{FlatMap, ~>}
import simulacrum.typeclass
@@ -12,6 +13,7 @@ trait RepK[U[_[_]], A] {

object RepK {
def apply[U[_[_]]] = new Applied[U](true)
def mk[U[_[_]]] = new Applied[U](true)

class Applied[T[_[_]]](private val __ : Boolean) extends AnyVal {
type Arb[_]
@@ -58,7 +60,102 @@ trait RepresentableKInstanceChain[TC[u[_[_]]] >: RepresentableK[u]] {
override def pureK[F[_]](p: Point[F]): F[A] = p.point[A]
}

private[this] val idKRepresentableAny = idKRepresentableInst[Any]
private[this] def readerTInstance[R, A]: RepresentableK[ReaderT[*[_], R, A]] =
new RepresentableK[ReaderT[*[_], R, A]] {
def tabulate[F[_]](hom: RepK[ReaderT[*[_], R, A], *] ~> F): ReaderT[F, R, A] = ReaderT(r => hom(RepK.mk(_.run(r)))
)
override def embed[F[_]: FlatMap](ft: F[ReaderT[F, R, A]]): ReaderT[F, R, A] = ReaderT(r => ft.flatMap(_.run(r)))
override def pureK[F[_]](p: Point[F]): ReaderT[F, R, A] = ReaderT(r => p.point[A])
override val unitK: ReaderT[UnitK, R, A] = super.unitK
override def mapK[F[_], G[_]](af: ReaderT[F, R, A])(fk: F ~> G): ReaderT[G, R, A] = af.mapK(fk)

override def productK[F[_], G[_]](af: ReaderT[F, R, A], ag: ReaderT[G, R, A]): ReaderT[Tuple2K[F, G, *], R, A] =
ReaderT(r => Tuple2K(af.run(r), ag.run(r)))

override def zipWith2K[F[_], G[_], H[_]](af: ReaderT[F, R, A], ag: ReaderT[G, R, A])(
f2: Function2K[F, G, H]
): ReaderT[H, R, A] = ReaderT(r => f2(af.run(r), ag.run(r)))
}

private[this] def optionTInstance[A]: RepresentableK[OptionT[*[_], A]] = new RepresentableK[OptionT[*[_], A]] {
def tabulate[F[_]](hom: RepK[OptionT[*[_], A], *] ~> F): OptionT[F, A] = OptionT(hom(RepK.mk(_.value)))

override def mapK[F[_], G[_]](af: OptionT[F, A])(fk: F ~> G): OptionT[G, A] = af.mapK(fk)
override def productK[F[_], G[_]](af: OptionT[F, A], ag: OptionT[G, A]): OptionT[Tuple2K[F, G, *], A] =
OptionT(Tuple2K(af.value, ag.value))
override def zipWith2K[F[_], G[_], H[_]](af: OptionT[F, A], ag: OptionT[G, A])(
f2: Function2K[F, G, H]
): OptionT[H, A] =
OptionT(f2(af.value, ag.value))
override def pureK[F[_]](p: Point[F]): OptionT[F, A] = OptionT(p.point)
override val unitK: OptionT[UnitK, A] = super.unitK
override def embed[F[_]: FlatMap](ft: F[OptionT[F, A]]): OptionT[F, A] = OptionT(ft.flatMap(_.value))
}

private[this] def eitherTInstance[E, A]: RepresentableK[EitherT[*[_], E, A]] =
new RepresentableK[EitherT[*[_], E, A]] {
def tabulate[F[_]](hom: RepK[EitherT[*[_], E, A], *] ~> F): EitherT[F, E, A] =
EitherT(hom(RepK.mk(_.value)))

override def mapK[F[_], G[_]](af: EitherT[F, E, A])(fk: F ~> G): EitherT[G, E, A] = af.mapK(fk)
override def productK[F[_], G[_]](af: EitherT[F, E, A], ag: EitherT[G, E, A]): EitherT[Tuple2K[F, G, *], E, A] =
EitherT(Tuple2K(af.value, ag.value))
override def zipWith2K[F[_], G[_], H[_]](af: EitherT[F, E, A], ag: EitherT[G, E, A])(f2: Function2K[F, G, H]): EitherT[H, E, A] =
EitherT(f2(af.value, ag.value))
override def pureK[F[_]](p: Point[F]): EitherT[F, E, A] =
EitherT(p.point)
override val unitK: EitherT[UnitK, E, A] = super.unitK
override def embed[F[_]: FlatMap](ft: F[EitherT[F, E, A]]): EitherT[F, E, A] = EitherT(ft.flatMap(_.value))
}

private[this] def writerTInstance[W, A]: RepresentableK[WriterT[*[_], W, A]] =
new RepresentableK[WriterT[*[_], W, A]] {

def tabulate[F[_]](hom: RepK[WriterT[*[_], W, A], *] ~> F): WriterT[F, W, A] = WriterT(hom(RepK.mk(_.run)))

override def mapK[F[_], G[_]](af: WriterT[F, W, A])(fk: F ~> G): WriterT[G, W, A] = af.mapK(fk)
override def productK[F[_], G[_]](af: WriterT[F, W, A], ag: WriterT[G, W, A]): WriterT[Tuple2K[F, G, *], W, A] =
WriterT(Tuple2K(af.run, ag.run))
override def zipWith2K[F[_], G[_], H[_]](af: WriterT[F, W, A], ag: WriterT[G, W, A])(f2: Function2K[F, G, H]): WriterT[H, W, A] =
WriterT(f2(af.run, ag.run))
override def pureK[F[_]](p: Point[F]): WriterT[F, W, A] = WriterT(p.point)
override val unitK: WriterT[UnitK, W, A] = super.unitK
override def embed[F[_]: FlatMap](ft: F[WriterT[F, W, A]]): WriterT[F, W, A] = WriterT(ft.flatMap(_.run))
}

private[this] def iorTInstance[E, A]: RepresentableK[IorT[*[_], E, A]] = new RepresentableK[IorT[*[_], E, A]] {

def tabulate[F[_]](hom: RepK[IorT[*[_], E, A], *] ~> F): IorT[F, E, A] = IorT(hom(RepK.mk(_.value)))

override def mapK[F[_], G[_]](af: IorT[F, E, A])(fk: F ~> G): IorT[G, E, A] =
af.mapK(fk)
override def productK[F[_], G[_]](af: IorT[F, E, A], ag: IorT[G, E, A]): IorT[Tuple2K[F, G, *], E, A] =
IorT(Tuple2K(af.value, ag.value))
override def zipWith2K[F[_], G[_], H[_]](af: IorT[F, E, A], ag: IorT[G, E, A])(f2: Function2K[F, G, H]): IorT[H, E, A] =
IorT(f2(af.value, ag.value))
override def pureK[F[_]](p: Point[F]): IorT[F, E, A] = IorT(p.point)
override val unitK: IorT[UnitK, E, A] = super.unitK
override def embed[F[_]: FlatMap](ft: F[IorT[F, E, A]]): IorT[F, E, A] = IorT(ft.flatMap(_.value))
}

private[this] val optionTRepresentableKAny = optionTInstance[Any]
private[this] val eitherTRepresentableKAny = eitherTInstance[Any, Any]
private[this] val writerTRepresentableKAny = writerTInstance[Any, Any]
private[this] val iorTRepresentableKAny = iorTInstance[Any, Any]
private[this] val idKRepresentableAny = idKRepresentableInst[Any]
private[this] val readerTInstanceAny = readerTInstance[Any, Any]

final implicit def idKRepresentable[A]: TC[IdK[A]#λ] = idKRepresentableAny.asInstanceOf[TC[IdK[A]#λ]]
final implicit def readerTRepresentable[R, A]: TC[ReaderT[*[_], R, A]] =
readerTInstanceAny.asInstanceOf[TC[ReaderT[*[_], R, A]]]

final implicit def optionRepresentableK[A]: TC[OptionT[*[_], A]] =
optionTRepresentableKAny.asInstanceOf[TC[OptionT[*[_], A]]]
final implicit def eitherTRepresentableK[E, A]: TC[EitherT[*[_], E, A]] =
eitherTRepresentableKAny.asInstanceOf[TC[EitherT[*[_], E, A]]]
final implicit def writerTRepresentableK[W, A]: TC[WriterT[*[_], W, A]] =
writerTRepresentableKAny.asInstanceOf[TC[WriterT[*[_], W, A]]]
final implicit def iorTRepresentableK[E, A]: TC[IorT[*[_], E, A]] =
iorTRepresentableKAny.asInstanceOf[TC[IorT[*[_], E, A]]]

}
Original file line number Diff line number Diff line change
@@ -18,28 +18,36 @@ class HigherKindedMacros(override val c: blackbox.Context) extends cats.tagless.

def tabulate(algebra: Type): (String, Type => Tree) =
"tabulate" -> {
case PolyType(List(f), MethodType(List(hom), _)) =>
val members = overridableMembersOf(algebra)
val types = delegateAbstractTypes(algebra, members, algebra)
case PolyType(List(f), MethodType(List(hom), af)) =>

val members = overridableMembersOf(af)
val types = delegateAbstractTypes(af, members, af)
val repk = reify(tofu.higherKind.RepK).tree
val funk = reify(tofu.syntax.functionK).tree
val alg = TermName(c.freshName("alg"))
val rep = TermName(c.freshName("rep"))
val et = tq""
val algv = q"val $alg: $et"
val ff = algebra match {
case PolyType(List(ff1), _) => ff1
}
val methods = delegateMethods(algebra, members, NoSymbol) {
case method if method.occursInParams(ff) =>
abort(s"Type parameter $ff appears in contravariant position in method ${method.name}")
val repv = q"val $rep: $et"

case method if method.occursInReturn(ff) =>
val methods = delegateMethods(af, members, NoSymbol) {
case method if method.occursInParams(f) =>
abort(s"Type parameter $f appears in contravariant position in method ${method.name}")

case method if method.returnType.typeConstructor.typeSymbol == f =>
val params = method.paramLists.map(_.map(_.name))
val body = q"$hom($repk[$algebra](($algv => $alg.${method.name}(...$params))))"
val tpe = appliedType(f, method.returnType.typeArgs)
method.copy(body = body, returnType = tpe)
method.copy(body = body)

case method if method.occursInReturn(f) =>
val params = method.paramLists.map(_.map(_.name))
val tt = polyType(f :: Nil, method.returnType)
val F = summon[RepresentableK[Any]](tt)
val body = q"$F.tabulate($funk.funK($repv => $hom($repk[$algebra]($algv => $rep($alg.${method.name}(...$params))))))"
method.copy(body = body)

case method =>
abort(s"Type parameter $ff does not appear in return in method ${method.name}")
abort(s"Type parameter $f does not appear in return in method ${method.name}")
}

val res = implement(algebra)(f)(types ++ methods)