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 all commits
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
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,21 @@
package tofu.higherKind.derived

import cats.data.Tuple2K
import RepresentableKSuite.Foo
import cats.data.{OptionT, Tuple2K}
import cats.instances.either._
import cats.instances.option._
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.syntax.traverse._
import cats.syntax.option._
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.syntax.embed._
import tofu.syntax.functionK.funK

import scala.util.Try

@@ -25,30 +25,38 @@ 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[String]): OptionT[Either[String, *], Unit] =
OptionT(a.headOption.traverse(_.asLeft[Unit]))
}

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[String]): OptionT[Id, Unit] = OptionT.none
}

type MapR[+A] = Embedded[(String, +*), List, A]

"representableK" should "generate nice mapK" in {
val eitherToList: Either[String, *] ~> Embedded[(String, +*), List, *] = funK {
val eitherToList: Either[String, *] ~> MapR = funK {
case Left(err) => Embedded(((err, Nil)))
case Right(res) => Embedded((("", List(res))))
}

val mappedFoo = checkingFoo.mapK(eitherToList)
val mappedFoo: Foo[MapR] = checkingFoo.mapK(eitherToList)

mappedFoo.foo(2, "2.3") should ===(Embedded(("", List(4.6))))
mappedFoo.foo(2, "fail") should ===(Embedded(("could not parse fail as double", List())))

mappedFoo.bar(List(4, 5, 6)) should ===(Embedded(("", List(()))))
mappedFoo.bar(List()) should ===(Embedded(("must contain at least one element", List())))

mappedFoo.baz(List("one", "two")) should ===(OptionT[MapR, Unit](Embedded(("one", Nil))))
mappedFoo.baz(Nil) should ===(OptionT[MapR, Unit](Embedded(("", List(None)))))
}

"representableK" should "generate nice productK" in {
val zippedFoo = checkingFoo.productK(defaultFoo)
val zippedFoo: Foo[Tuple2K[Either[String, *], Id, *]] = checkingFoo.productK(defaultFoo)

def tuple[A](e: Either[String, A], a: A) = Tuple2K[Either[String, *], Id, A](e, a)

@@ -57,6 +65,10 @@ class RepresentableKSuite extends FlatSpec with Matchers {

zippedFoo.bar(List(4, 5, 6)) should ===(tuple(Right(()), ()))
zippedFoo.bar(List()) should ===(tuple(Left("must contain at least one element"), ()))

zippedFoo.baz(List("one", "two")) should ===(OptionT(tuple(Left("one"), none[Unit])))
zippedFoo.baz(Nil) should ===(OptionT(tuple(Right(none[Unit]), none[Unit])))

}

"representableK" should "generate nice embed" in {
@@ -82,6 +94,9 @@ 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[String]): OptionT[F, Unit]
}
}
35 changes: 2 additions & 33 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,8 @@ 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] 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 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 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]]]
}
107 changes: 105 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,108 @@ 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"

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.occursInReturn(ff) =>
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)
@@ -79,7 +87,7 @@ class HigherKindedMacros(override val c: blackbox.Context) extends cats.tagless.
}

def representableK[Alg[_[_]]](implicit tag: WeakTypeTag[Alg[Any]]): Tree =
instantiate[RepresentableK[Alg]](tag)(tabulate, productK, mapK)
instantiate[RepresentableK[Alg]](tag)(tabulate, productK, mapK, embedf)

def embed[Alg[_[_]]](implicit tag: WeakTypeTag[Alg[Any]]): Tree =
instantiate[Embed[Alg]](tag)(embedf)