diff --git a/modules/core/src/main/scala/gql/preparation/QueryPreparation.scala b/modules/core/src/main/scala/gql/preparation/QueryPreparation.scala index 402c379d4..42b5f90c6 100644 --- a/modules/core/src/main/scala/gql/preparation/QueryPreparation.scala +++ b/modules/core/src/main/scala/gql/preparation/QueryPreparation.scala @@ -78,8 +78,9 @@ class QueryPreparation[F[_], C]( prepareStep[I2, O2](step, fieldMeta, uec append edge) step match { - case Step.Alg.Lift(f) => liftK(nextNodeId).map(PreparedStep.Lift(_, f)) - case Step.Alg.EmbedError() => liftK(nextNodeId).map(PreparedStep.EmbedError[F, O](_)) + case _: Step.Alg.Identity[I] => liftK(nextNodeId).map(PreparedStep.Lift(_, identity)) + case Step.Alg.Lift(f) => liftK(nextNodeId).map(PreparedStep.Lift(_, f)) + case Step.Alg.EmbedError() => liftK(nextNodeId).map(PreparedStep.EmbedError[F, O](_)) case alg: Step.Alg.Compose[F, i, a, o] => val left = rec[i, a](alg.left, "compose-left") val right = rec[a, o](alg.right, "compose-right") diff --git a/modules/core/src/main/scala/gql/resolver/Resolver.scala b/modules/core/src/main/scala/gql/resolver/Resolver.scala index 7fc50af9e..ea8af6e56 100644 --- a/modules/core/src/main/scala/gql/resolver/Resolver.scala +++ b/modules/core/src/main/scala/gql/resolver/Resolver.scala @@ -67,6 +67,8 @@ final class Resolver[+F[_], -I, +O](private[gql] val underlying: Step[F, I, O]) def step: Step[F, I, O] = underlying def covaryAll[F2[x] >: F[x], O2 >: O]: Resolver[F2, I, O2] = this + + def optimize: Resolver[F, I, O] = new Resolver(Step.optimize(underlying)) } object Resolver extends ResolverInstances { @@ -80,7 +82,7 @@ object Resolver extends ResolverInstances { def lift[F[_], I]: PartiallyAppliedLift[F, I] = new PartiallyAppliedLift[F, I] def id[F[_], I]: Resolver[F, I, I] = - lift(identity) + new Resolver(Step.identity) def effectFull[F[_], I, O](f: I => F[O]): Resolver[F, I, O] = liftFull(f).andThen(new Resolver(Step.embedEffect)) @@ -210,5 +212,7 @@ trait ResolverInstances { new Resolver(Step.first(fa.underlying)) override def lift[A, B](f: A => B): Resolver[F, A, B] = Resolver.lift(f) + + override def id[A]: Resolver[F, A, A] = Resolver.id } } diff --git a/modules/core/src/main/scala/gql/resolver/Step.scala b/modules/core/src/main/scala/gql/resolver/Step.scala index 74978e796..498879ebe 100644 --- a/modules/core/src/main/scala/gql/resolver/Step.scala +++ b/modules/core/src/main/scala/gql/resolver/Step.scala @@ -17,6 +17,8 @@ package gql.resolver import gql._ import cats.data._ +import cats.implicits._ +import cats._ /** A step is a composable task that takes an input and produces an output. */ @@ -24,32 +26,64 @@ sealed trait Step[+F[_], -I, +O] object Step { object Alg { - final case class Lift[I, O](f: I => O) extends AnyRef with Step[Nothing, I, O] + final case class Identity[I]() extends AnyRef with Step[Nothing, I, I] { + override def toString: String = s"Identity" + } + + final case class Lift[I, O](f: I => O) extends AnyRef with Step[Nothing, I, O] { + override def toString: String = s"Lift(...)" + } - final case class EmbedEffect[F[_], I]() extends AnyRef with Step[F, F[I], I] + final case class EmbedEffect[F[_], I]() extends AnyRef with Step[F, F[I], I] { + override def toString: String = s"EmbedEffect" + } final case class EmbedStream[F[_], I]( signal: Boolean ) extends AnyRef - with Step[F, fs2.Stream[F, I], I] + with Step[F, fs2.Stream[F, I], I] { + override def toString: String = s"EmbedStream(signal=$signal)" + } - final case class EmbedError[I]() extends AnyRef with Step[Nothing, Ior[String, I], I] + final case class EmbedError[I]() extends AnyRef with Step[Nothing, Ior[String, I], I] { + override def toString: String = s"EmbedError" + } - final case class Argument[I, A](arg: Arg[A]) extends Step[Nothing, I, A] + final case class Argument[I, A](arg: Arg[A]) extends Step[Nothing, I, A] { + override def toString: String = s"Argument(${arg.entries.map(_.name).mkString_(", ")})" + } - final case class Compose[F[_], I, A, O](left: Step[F, I, A], right: Step[F, A, O]) extends Step[F, I, O] + final case class Compose[F[_], I, A, O](left: Step[F, I, A], right: Step[F, A, O]) extends Step[F, I, O] { + override def toString: String = s"Compose($left, $right)" + } - final case class Choose[F[_], A, B, C, D](fac: Step[F, A, C], fab: Step[F, B, D]) extends Step[F, Either[A, B], Either[C, D]] + final case class Choose[F[_], A, B, C, D]( + fac: Step[F, A, C], + fab: Step[F, B, D] + ) extends Step[F, Either[A, B], Either[C, D]] { + override def toString: String = s"Choose($fac, $fab)" + } - final case class GetMeta[F[_], I]() extends Step[Nothing, I, FieldMeta[F]] + final case class GetMeta[F[_], I]() extends Step[Nothing, I, FieldMeta[F]] { + override def toString: String = s"GetMeta" + } - final case class First[F[_], A, B, C](step: Step[F, A, B]) extends Step[F, (A, C), (B, C)] + final case class First[F[_], A, B, C](step: Step[F, A, B]) extends Step[F, (A, C), (B, C)] { + override def toString: String = s"First($step)" + } - final case class Batch[F[_], K, V](id: BatchKey[K, V]) extends Step[F, Set[K], Map[K, V]] + final case class Batch[F[_], K, V](id: BatchKey[K, V]) extends Step[F, Set[K], Map[K, V]] { + override def toString: String = s"Batch($id)" + } - final case class InlineBatch[F[_], K, V](run: Set[K] => F[Map[K, V]]) extends Step[F, Set[K], Map[K, V]] + final case class InlineBatch[F[_], K, V](run: Set[K] => F[Map[K, V]]) extends Step[F, Set[K], Map[K, V]] { + override def toString: String = s"InlineBatch" + } } + def identity[I]: Step[Nothing, I, I] = + Alg.Identity() + def lift[F[_], I, O](f: I => O): Step[F, I, O] = Alg.Lift(f) @@ -92,6 +126,77 @@ object Step { def inlineBatch[F[_], K, V](f: Set[K] => F[Map[K, V]]): Step[F, Set[K], Map[K, V]] = Alg.InlineBatch(f) + def optimize[F[_], I, O](step: Step[F, I, O]): Step[F, I, O] = { + sealed trait CurrentContinuation[A, B] { + def apply[A0](s: Step[F, A0, A]): Step[F, A0, B] + def push[C](f: Step[F, C, A]): CurrentContinuation[C, B] = + CurrentContinuation.CC(f, this) + } + object CurrentContinuation { + final case class Identity[A]() extends CurrentContinuation[A, A] { + def apply[A0](s: Step[F, A0, A]): Step[F, A0, A] = s + } + final case class CC[A, B, C]( + l: Step[F, A, B], + r: CurrentContinuation[B, C] + ) extends CurrentContinuation[A, C] { + def apply[A0](s: Step[F, A0, A]): Step[F, A0, C] = + r(Step.Alg.Compose(s, l)) + } + } + + def optimizeCompose[A1, B1](s: Step[F, A1, B1]): Step[F, A1, B1] = s match { + case alg: Alg.Compose[F, A1, a, B1] => + type A = a + val e = (alg.left, alg.right) match { + case (_: Alg.Identity[i], r) => r.some + case (l, _: Alg.Identity[o]) => l.some + case (l: Alg.Lift[i, A], r: Alg.Lift[A, o]) => Alg.Lift(r.f.compose(l.f)).some + case (l: Alg.Lift[i, A], rc: Alg.Compose[F, A, a2, o]) => + rc.left match { + case rl: Alg.Lift[A, o] => Alg.Compose(Alg.Lift(rl.f.compose(l.f)), rc.right).some + case _ => None + } + case _ => None + } + e.map(_.asInstanceOf[Step[F, A1, B1]]).getOrElse(alg) + case _ => s + } + + def aux[A0, B0, C0]( + step: Step[F, A0, B0], + cc: CurrentContinuation[B0, C0] + ): Eval[Step[F, A0, C0]] = Eval.defer[Step[F, A0, C0]] { + step match { + case alg: Alg.Compose[F, i, a, o] => + aux(alg.right, cc).flatMap { r => + aux(alg.left, CurrentContinuation.CC(r, CurrentContinuation.Identity[C0]())).map(optimizeCompose) + } + case alg: Alg.Choose[F, a, b, c, d] => + ( + aux(alg.fac, CurrentContinuation.Identity[c]()), + aux(alg.fab, CurrentContinuation.Identity[d]()) + ).mapN(Alg.Choose[F, a, b, c, d](_, _)).map(x => optimizeCompose(cc(x))) + + case alg: Alg.First[F, a, b, c] => + type A = a + type B = b + aux(alg.step, CurrentContinuation.Identity[b]()) + .map { x => + val e = x match { + case _: Alg.Identity[?] => Alg.Identity[(A, c)]().some + case alg: Alg.Lift[A, B] => Alg.Lift[(A, c), (B, c)] { case (a, c) => (alg.f(a), c) }.some + case _ => None + } + e.map(_.asInstanceOf[Step[F, (A, c), (B, c)]]).getOrElse(Alg.First[F, a, b, c](x)) + } + .map(x => optimizeCompose(cc(x))) + case _ => Eval.now(optimizeCompose(cc(step))) + } + } + aux(step, CurrentContinuation.Identity[O]()).value + } + import cats.arrow._ implicit def arrowChoiceForStep[F[_]]: ArrowChoice[Step[F, *, *]] = new ArrowChoice[Step[F, *, *]] { override def choose[A, B, C, D](f: Step[F, A, C])(g: Step[F, B, D]): Step[F, Either[A, B], Either[C, D]] = @@ -102,5 +207,8 @@ object Step { override def first[A, B, C](fa: Step[F, A, B]): Step[F, (A, C), (B, C)] = Step.first(fa) override def lift[A, B](f: A => B): Step[F, A, B] = Step.lift(f) + + // optimization + override def id[A]: Step[F, A, A] = Step.identity } } diff --git a/modules/server/src/test/scala/gql/SkipTest.scala b/modules/server/src/test/scala/gql/SkipTest.scala index bc4f26448..5cb6ffb0d 100644 --- a/modules/server/src/test/scala/gql/SkipTest.scala +++ b/modules/server/src/test/scala/gql/SkipTest.scala @@ -34,7 +34,7 @@ class SkipTest extends CatsEffectSuite { case None => IO(Left(20)) case Some(i) => IO.pure(Right(i)) } - }.leftThrough(_.evalMap(i => effectState.modify(_ => (Some(i), i)))).map(_.merge) + }.leftThrough(_.evalMap(i => effectState.modify(_ => (Some(i), i)))).map(_.merge).optimize ) ) }