From d7dba77ea104495c4cc3d808f1e3bae5736cadde Mon Sep 17 00:00:00 2001 From: Kalin-Rudnicki Date: Mon, 1 Apr 2024 03:03:16 -0600 Subject: [PATCH] Added K11 kind to deriving --- .../src/main/scala/harness/deriving/K0.scala | 31 ++-- .../src/main/scala/harness/deriving/K1.scala | 35 ++-- .../src/main/scala/harness/deriving/K11.scala | 153 ++++++++++++++++++ .../deriving/ExampleK11DerivationSpec.scala | 83 ++++++++++ .../harness/deriving/ExampleK11Types.scala | 38 +++++ .../deriving/ExampleK1DerivationSpec.scala | 4 +- 6 files changed, 304 insertions(+), 40 deletions(-) create mode 100644 modules/harness-deriving/shared/src/main/scala/harness/deriving/K11.scala create mode 100644 modules/harness-deriving/shared/src/test/scala/harness/deriving/ExampleK11DerivationSpec.scala create mode 100644 modules/harness-deriving/shared/src/test/scala/harness/deriving/ExampleK11Types.scala diff --git a/modules/harness-deriving/shared/src/main/scala/harness/deriving/K0.scala b/modules/harness-deriving/shared/src/main/scala/harness/deriving/K0.scala index 2ff5e05..f16186a 100644 --- a/modules/harness-deriving/shared/src/main/scala/harness/deriving/K0.scala +++ b/modules/harness-deriving/shared/src/main/scala/harness/deriving/K0.scala @@ -25,14 +25,16 @@ abstract class K0T[UB] { inline def summonFieldInstances[T <: Tuple, F[_], G[_ <: UB]]: List[F[G[UB]]] = summonAll[FieldInstances[T, F, G]].toIArray.toList.asInstanceOf[List[F[G[UB]]]] - final case class ProductInstances[F <: UB, T[_ <: UB]]( - m: Mirror.ProductOf[F], - ev: F <:< Product, - rawInstances: List[LazyDerived[T[UB]]], + final class ProductInstances[F <: UB, T[_ <: UB]](val m: ProductGeneric[F])( + val ev: m.MirroredMonoType <:< Product, + val rawInstances: List[LazyDerived[T[UB]]], ) { lazy val instances: List[T[UB]] = rawInstances.map(_.derived) + def instantiate(fields: List[m.MirroredType]): F = + m.asInstanceOf[Mirror.ProductOf[m.MirroredMonoType]].fromTuple(Tuple.fromArray(fields.toArray[Any]).asInstanceOf) + final case class withInstance(a: F) { private def productElements: List[UB] = ev(a).productIterator.toList.asInstanceOf[List[UB]] @@ -76,24 +78,20 @@ abstract class K0T[UB] { } object ProductInstances { inline given of[F <: UB, T[_ <: UB]](using m: ProductGeneric[F]): ProductInstances[F, T] = - ProductInstances[F, T]( - m, - summonInline[F <:< Product], - // summonAll[FieldInstances[m.MirroredElemTypes, LazyDerived, F]].toIArray.toList.asInstanceOf[List[LazyDerived[F[UB]]]], + new ProductInstances[F, T](m)( + summonInline[m.MirroredMonoType <:< Product], summonFieldInstances[m.MirroredElemTypes, LazyDerived, T], ) } - final case class SumInstances[F <: UB, T[_ <: UB]]( - m: Mirror.SumOf[F], - children: List[T[UB]], + final class SumInstances[F <: UB, T[_ <: UB]](val m: SumGeneric[F])( + val children: List[T[UB]], ) { - def narrow[G[B <: UB] <: T[B]](implicit fCt: ClassTag[T[UB]], gCt: ClassTag[G[UB]]): SumInstances[F, G] = - SumInstances[F, G]( - m, + def narrow[G[B <: UB] <: T[B]](implicit fCt: ClassTag[T[m.MirroredType]], gCt: ClassTag[G[m.MirroredType]]): SumInstances[F, G] = + new SumInstances[F, G](m)( children.asInstanceOf[List[Matchable]].map { - case gCt(c) => c + case gCt(c) => c.asInstanceOf case other => throw new RuntimeException(s"Unable to narrow ${fCt.runtimeClass.getName} to ${gCt.runtimeClass.getName} ($other)") }, ) @@ -106,8 +104,7 @@ abstract class K0T[UB] { } object SumInstances { inline given of[A <: UB, F[_ <: UB]](using m: SumGeneric[A]): SumInstances[A, F] = - SumInstances[A, F]( - m, + new SumInstances[A, F](m)( summonFieldInstances[m.MirroredElemTypes, Derived, F].map(_.derived), ) } diff --git a/modules/harness-deriving/shared/src/main/scala/harness/deriving/K1.scala b/modules/harness-deriving/shared/src/main/scala/harness/deriving/K1.scala index b89d8bd..09f3af6 100644 --- a/modules/harness-deriving/shared/src/main/scala/harness/deriving/K1.scala +++ b/modules/harness-deriving/shared/src/main/scala/harness/deriving/K1.scala @@ -36,17 +36,16 @@ abstract class K1T[UB] { inline def summonFieldInstances[T <: [_ <: UB] =>> Tuple, F[_], G[_[_ <: UB]]]: List[F[G[[_ <: UB] =>> Any]]] = summonAll[FieldInstances[T, F, G]].toIArray.toList.asInstanceOf[List[F[G[[_ <: UB] =>> Any]]]] - // TODO (KR) : remove - type AnyF[X <: UB] = Any - - final case class ProductInstances[F[_ <: UB], T[_[_ <: UB]]]( - m: ProductGeneric[F], - ev: F[UB] <:< Product, - rawInstances: List[LazyDerived[T[[_ <: UB] =>> Any]]], + final class ProductInstances[F[_ <: UB], T[_[_ <: UB]]](val m: ProductGeneric[F])( + val ev: m.MirroredMonoType <:< Product, + val rawInstances: List[LazyDerived[T[[_ <: UB] =>> Any]]], ) { lazy val instances: List[T[[_ <: UB] =>> Any]] = rawInstances.map(_.derived) + def instantiate[A <: UB](fields: List[m.MirroredType[A]]): F[A] = + m.asInstanceOf[Mirror.ProductOf[m.MirroredMonoType]].fromTuple(Tuple.fromArray(fields.toArray[Any]).asInstanceOf).asInstanceOf[F[A]] + final case class withInstance[A <: UB](a: F[A]) { private def productElements: List[T[[_ <: UB] =>> Any]] = ev(a.asInstanceOf).productIterator.toList.asInstanceOf[List[T[[_ <: UB] =>> Any]]] @@ -64,12 +63,10 @@ abstract class K1T[UB] { object mapInstantiate { def apply[B <: UB](f: [t[_ <: UB]] => (T[t], t[A]) => t[B]): F[B] = - m.asInstanceOf[Mirror.ProductOf[F[B]]] - .fromTuple(Tuple.fromArray(map(f).toArray).asInstanceOf) + instantiate(map(f).asInstanceOf) def withLabels[B <: UB](labels: Labelling[m.MirroredMonoType])(f: [t[_ <: UB]] => (String, T[t], t[A]) => t[B]): F[B] = - m.asInstanceOf[Mirror.ProductOf[F[B]]] - .fromTuple(Tuple.fromArray(map.withLabels(labels)(f).toArray).asInstanceOf) + instantiate(map.withLabels(labels)(f).asInstanceOf) } @@ -78,7 +75,7 @@ abstract class K1T[UB] { def apply[R](z: R)(f: [t[_ <: UB]] => (R, T[t], t[A]) => R): R = productElements.zip(instances).foldLeft(z) { case (acc, (b, i)) => f(acc, i, b) } - def withLabels[R](labels: Labelling[m.MirroredMonoType], z: R)(f: [t[_ <: UB]] => (R, String, T[t], t[UB]) => R): R = + def withLabels[R](labels: Labelling[m.MirroredMonoType], z: R)(f: [t[_ <: UB]] => (R, String, T[t], t[A]) => R): R = labels.elemLabels.zip(productElements).zip(instances).foldLeft(z) { case (acc, ((l, b), i)) => f(acc, l, i, b) } } @@ -102,22 +99,19 @@ abstract class K1T[UB] { } object ProductInstances { inline given of[F[_ <: UB], T[_[_ <: UB]]](using m: ProductGeneric[F]): ProductInstances[F, T] = - ProductInstances[F, T]( - m, - summonInline[ProductGeneric[F]#MirroredMonoType <:< Product], + new ProductInstances[F, T](m)( + summonInline[m.MirroredMonoType <:< Product], summonFieldInstances[m.MirroredElemTypes, LazyDerived, T], ) } - final case class SumInstances[F[_ <: UB], T[_[_ <: UB]]]( - m: SumGeneric[F], + final class SumInstances[F[_ <: UB], T[_[_ <: UB]]](val m: SumGeneric[F])( children: List[T[[_ <: UB] =>> Any]], ) { // TODO (KR) : Im not sure on this one... def narrow[G[B[_ <: UB]] <: T[B]](implicit fCt: ClassTag[T[m.MirroredType]], gCt: ClassTag[G[m.MirroredType]]): SumInstances[F, G] = - SumInstances[F, G]( - m, + new SumInstances[F, G](m)( children.asInstanceOf[List[Matchable]].map { case gCt(c) => c.asInstanceOf case other => throw new RuntimeException(s"Unable to narrow ${fCt.runtimeClass.getName} to ${gCt.runtimeClass.getName} ($other)") @@ -132,8 +126,7 @@ abstract class K1T[UB] { } object SumInstances { inline given of[F[_ <: UB], T[_[_ <: UB]]](using m: SumGeneric[F]): SumInstances[F, T] = - SumInstances[F, T]( - m, + new SumInstances[F, T](m)( summonFieldInstances[m.MirroredElemTypes, Derived, T].map(_.derived), ) } diff --git a/modules/harness-deriving/shared/src/main/scala/harness/deriving/K11.scala b/modules/harness-deriving/shared/src/main/scala/harness/deriving/K11.scala new file mode 100644 index 0000000..495ec5c --- /dev/null +++ b/modules/harness-deriving/shared/src/main/scala/harness/deriving/K11.scala @@ -0,0 +1,153 @@ +package harness.deriving + +import scala.compiletime.* +import scala.deriving.Mirror +import scala.reflect.ClassTag + +abstract class K11T[UB] { + + type Kind[C, O[_[_ <: UB]]] = C { + type MirroredType[X[_ <: UB]] = O[X] + type MirroredMonoType = O[[_ <: UB] =>> Any] + type MirroredElemTypes[_[_ <: UB]] <: Tuple + } + + type Generic[O[_[_ <: UB]]] = Kind[Mirror, O] + type ProductGeneric[O[_[_ <: UB]]] = Kind[Mirror.Product, O] + type SumGeneric[O[_[_ <: UB]]] = Kind[Mirror.Sum, O] + + type Id[t <: UB] = [f[_ <: UB]] =>> f[t] + type Identity[t <: UB] = t + type Const[c] = [_[_ <: UB]] =>> c + type ~>[A[_ <: UB], B[_ <: UB]] = [t <: UB] => A[t] => B[t] + + type Head[T <: [_[_ <: UB]] =>> Any, A[_ <: UB]] = + T[A] match { + case h *: ? => h + } + type Tail[T <: [_[_ <: UB]] =>> Any, A[_ <: UB]] = + T[A] match { + case ? *: t => t + } + + type FieldInstances[T <: [_[_ <: UB]] =>> Tuple, F[_], G[_[_[_ <: UB]]]] <: Tuple = + T[Option] match { + case ? *: ? => F[G[[X[_ <: UB]] =>> Head[T, X]]] *: FieldInstances[[X[_ <: UB]] =>> Tail[T, X], F, G] + case EmptyTuple => EmptyTuple + } + + inline def summonFieldInstances[T <: [_[_ <: UB]] =>> Tuple, F[_], G[_[_[_ <: UB]]]]: List[F[G[[_[_ <: UB]] =>> Any]]] = + summonAll[FieldInstances[T, F, G]].toIArray.toList.asInstanceOf[List[F[G[[_[_ <: UB]] =>> Any]]]] + + final class ProductInstances[F[_[_ <: UB]], T[_[_[_ <: UB]]]](val m: ProductGeneric[F])( + val ev: m.MirroredMonoType <:< Product, + val rawInstances: List[LazyDerived[T[[_[_ <: UB]] =>> Any]]], + ) { + + lazy val instances: List[T[[_[_ <: UB]] =>> Any]] = rawInstances.map(_.derived) + + def instantiate[A[_ <: UB]](fields: List[m.MirroredType[A]]): F[A] = + m.asInstanceOf[Mirror.ProductOf[m.MirroredMonoType]].fromTuple(Tuple.fromArray(fields.toArray[Any]).asInstanceOf).asInstanceOf[F[A]] + + final case class withInstance[A[_ <: UB]](a: F[A]) { + + private def productElements: List[T[[_[_ <: UB]] =>> Any]] = ev(a.asInstanceOf).productIterator.toList.asInstanceOf[List[T[[_[_ <: UB]] =>> Any]]] + + object map { + + def apply[B](f: [t[_[_ <: UB]]] => (T[t], t[A]) => B): List[B] = + productElements.zip(instances).map { case (b, i) => f(i, b) } + + def withLabels[B](labels: Labelling[m.MirroredMonoType])(f: [t[_[_ <: UB]]] => (String, T[t], t[A]) => B): List[B] = + labels.elemLabels.zip(productElements).zip(instances).map { case ((l, b), i) => f(l, i, b) } + + } + + object mapInstantiate { + + def apply[B[_ <: UB]](f: [t[_[_ <: UB]]] => (T[t], t[A]) => t[B]): F[B] = + instantiate(map(f).asInstanceOf) + + def withLabels[B[_ <: UB]](labels: Labelling[m.MirroredMonoType])(f: [t[_[_ <: UB]]] => (String, T[t], t[A]) => t[B]): F[B] = + instantiate(map.withLabels(labels)(f).asInstanceOf) + + } + + object foldLeft { + + def apply[R](z: R)(f: [t[_[_ <: UB]]] => (R, T[t], t[A]) => R): R = + productElements.zip(instances).foldLeft(z) { case (acc, (b, i)) => f(acc, i, b) } + + def withLabels[R](labels: Labelling[m.MirroredMonoType], z: R)(f: [t[_[_ <: UB]]] => (R, String, T[t], t[A]) => R): R = + labels.elemLabels.zip(productElements).zip(instances).foldLeft(z) { case (acc, ((l, b), i)) => f(acc, l, i, b) } + + } + + } + + object withoutInstance { + + object foldLeft { + + def apply[R](z: R)(f: [t[_[_ <: UB]]] => (R, T[t]) => R): R = + instances.foldLeft(z) { case (acc, i) => f(acc, i) } + + def withLabels[R](labels: Labelling[m.MirroredMonoType], z: R)(f: [t[_[_ <: UB]]] => (R, String, T[t]) => R): R = + labels.elemLabels.zip(instances).foldLeft(z) { case (acc, (l, i)) => f(acc, l, i) } + + } + + } + + } + object ProductInstances { + inline given of[F[_[_ <: UB]], T[_[_[_ <: UB]]]](using m: ProductGeneric[F]): ProductInstances[F, T] = + new ProductInstances[F, T](m)( + summonInline[m.MirroredMonoType <:< Product], + summonFieldInstances[m.MirroredElemTypes, LazyDerived, T], + ) + } + + final class SumInstances[F[_[_ <: UB]], T[_[_[_ <: UB]]]](val m: SumGeneric[F])( + children: List[T[[_[_ <: UB]] =>> Any]], + ) { + + // TODO (KR) : Im not sure on this one... + def narrow[G[B[_[_ <: UB]]] <: T[B]](implicit fCt: ClassTag[T[m.MirroredType]], gCt: ClassTag[G[m.MirroredType]]): SumInstances[F, G] = + new SumInstances[F, G](m)( + children.asInstanceOf[List[Matchable]].map { + case gCt(c) => c.asInstanceOf + case other => throw new RuntimeException(s"Unable to narrow ${fCt.runtimeClass.getName} to ${gCt.runtimeClass.getName} ($other)") + }, + ) + + final case class withInstance[A[_ <: UB]](a: F[A]) { + val ord: Int = m.ordinal(a.asInstanceOf) + val inst: T[F] = children(ord).asInstanceOf + } + + } + object SumInstances { + inline given of[F[_[_ <: UB]], T[_[_[_ <: UB]]]](using m: SumGeneric[F]): SumInstances[F, T] = + new SumInstances[F, T](m)( + summonFieldInstances[m.MirroredElemTypes, Derived, T].map(_.derived), + ) + } + + trait Derivable[T[_[_[_ <: UB]]]] { + + inline implicit def genProduct[F[_[_ <: UB]]](implicit m: ProductGeneric[F]): Derived[T[F]] + + inline implicit def genSum[F[_[_ <: UB]]](implicit m: SumGeneric[F]): Derived[T[F]] + + inline final def derive[F[_[_ <: UB]]](using m: Generic[F]): T[F] = + inline m match { + case m: ProductGeneric[F] => genProduct[F](using m).derived + case m: SumGeneric[F] => genSum[F](using m).derived + } + + } + +} + +object K11 extends K11T[Any] diff --git a/modules/harness-deriving/shared/src/test/scala/harness/deriving/ExampleK11DerivationSpec.scala b/modules/harness-deriving/shared/src/test/scala/harness/deriving/ExampleK11DerivationSpec.scala new file mode 100644 index 0000000..1efd9ec --- /dev/null +++ b/modules/harness-deriving/shared/src/test/scala/harness/deriving/ExampleK11DerivationSpec.scala @@ -0,0 +1,83 @@ +package harness.deriving + +import cats.syntax.option.* +import harness.deriving.ExampleK11Types.* +import harness.zio.* +import harness.zio.test.* +import zio.test.* + +object ExampleK11DerivationSpec extends DefaultHarnessSpec { + + import K11.~> + + trait FunctorK[F[_[_]]] { + def mapK[A[_], B[_]](a: F[A])(f: A ~> B): F[B] + } + object FunctorK extends K11.Derivable[FunctorK] { + + implicit def id[X]: FunctorK[K11.Id[X]] = + new FunctorK[K11.Id[X]] { + override def mapK[A[_], B[_]](a: K11.Id[X][A])(f: A ~> B): K11.Id[X][B] = f(a) + } + + override inline implicit def genProduct[F[_[_]]](implicit m: K11.ProductGeneric[F]): Derived[FunctorK[F]] = { + val inst = K11.ProductInstances.of[F, FunctorK] + + Derived { + new FunctorK[F] { + override def mapK[A[_], B[_]](a: F[A])(f: A ~> B): F[B] = + inst.withInstance(a).mapInstantiate { [t[_[_]]] => (i: FunctorK[t], t: t[A]) => i.mapK(t)(f) } + } + } + } + + override inline implicit def genSum[F[_[_]]](implicit m: K11.SumGeneric[F]): Derived[FunctorK[F]] = { + val inst = K11.SumInstances.of[F, FunctorK] + + Derived { + new FunctorK[F] { + override def mapK[A[_], B[_]](a: F[A])(f: A ~> B): F[B] = + inst.withInstance(a).inst.mapK(a)(f) + } + } + } + + } + + // =====| Instances |===== + + implicit val productSimpleFunctor: FunctorK[ProductSimple] = FunctorK.derive + implicit val sumSimpleFunctor: FunctorK[SumSimple] = FunctorK.derive + + // =====| Test |===== + + // override def logLevel: Logger.LogLevel = Logger.LogLevel.Debug + + override def spec: TestSpec = + suite("ExampleK11DerivationSpec")( + suite("ProductSimple")( + test("works-1") { + val input = ProductSimple[K11.Identity]( + string = "string", + int = 1, + boolean = true, + optString = "string2".some, + optInt = 2.some, + optBoolean = false.some, + ) + val mapped = productSimpleFunctor.mapK[K11.Identity, Option](input) { [t] => (a: K11.Identity[t]) => a.some } + val exp = ProductSimple[Option]( + string = "string".some, + int = 1.some, + boolean = true.some, + optString = "string2".some.some, + optInt = 2.some.some, + optBoolean = false.some.some, + ) + + assertTrue(mapped == exp) + }, + ), + ) + +} diff --git a/modules/harness-deriving/shared/src/test/scala/harness/deriving/ExampleK11Types.scala b/modules/harness-deriving/shared/src/test/scala/harness/deriving/ExampleK11Types.scala new file mode 100644 index 0000000..337b757 --- /dev/null +++ b/modules/harness-deriving/shared/src/test/scala/harness/deriving/ExampleK11Types.scala @@ -0,0 +1,38 @@ +package harness.deriving + +object ExampleK11Types { + + // =====| Simple |===== + + final case class ProductSimple[F[_]]( + string: F[String], + int: F[Int], + boolean: F[Boolean], + optString: F[Option[String]], + optInt: F[Option[Int]], + optBoolean: F[Option[Boolean]], + ) + + sealed trait SumSimple[F[_]] + object SumSimple { + + final case class Case1[F[_]]( + string: F[String], + int: F[Int], + boolean: F[Boolean], + ) extends SumSimple[F] + + final case class Case2[F[_]]( + optString: F[Option[String]], + optInt: F[Option[Int]], + optBoolean: F[Option[Boolean]], + ) extends SumSimple[F] + + final case class Case3[F[_]]( + product1: ProductSimple[F], + product2: F[ProductSimple[F]], + ) + + } + +} diff --git a/modules/harness-deriving/shared/src/test/scala/harness/deriving/ExampleK1DerivationSpec.scala b/modules/harness-deriving/shared/src/test/scala/harness/deriving/ExampleK1DerivationSpec.scala index da7ce6b..52679bc 100644 --- a/modules/harness-deriving/shared/src/test/scala/harness/deriving/ExampleK1DerivationSpec.scala +++ b/modules/harness-deriving/shared/src/test/scala/harness/deriving/ExampleK1DerivationSpec.scala @@ -54,11 +54,11 @@ object ExampleK1DerivationSpec extends DefaultHarnessSpec { } + // =====| Instances |===== + implicit val productSimpleFunctor: Functor[ProductSimple] = Functor.derive implicit val sumSimpleFunctor: Functor[SumSimple] = Functor.derive - // =====| Instances |===== - // =====| Test |===== // override def logLevel: Logger.LogLevel = Logger.LogLevel.Debug