diff --git a/kyo-data/shared/src/main/scala/kyo/TypeMap.scala b/kyo-data/shared/src/main/scala/kyo/TypeMap.scala index de37faa7b..24d4ea8be 100644 --- a/kyo-data/shared/src/main/scala/kyo/TypeMap.scala +++ b/kyo-data/shared/src/main/scala/kyo/TypeMap.scala @@ -66,7 +66,9 @@ object TypeMap: * A new TypeMap containing all key-value pairs from both TypeMaps */ inline def union[B](that: TypeMap[B]): TypeMap[A & B] = - self ++ that + if that.isEmpty then self + else if self.isEmpty then that + else self ++ that /** Filters the TypeMap to only include key-value pairs where the key is a subtype of the given type. * diff --git a/kyo-prelude/shared/src/main/scala/kyo/Layer.scala b/kyo-prelude/shared/src/main/scala/kyo/Layer.scala index 70ca8e54d..339200a9e 100644 --- a/kyo-prelude/shared/src/main/scala/kyo/Layer.scala +++ b/kyo-prelude/shared/src/main/scala/kyo/Layer.scala @@ -47,12 +47,10 @@ abstract class Layer[+Out, -S] extends Serializable: * The output type of the composed layer * @tparam S2 * Additional effects of the composed layer - * @tparam In2 - * The input type required by the second layer * @return * A new layer representing the composition of both layers */ - final infix def to[Out2, S2, In2](that: Layer[Out2, Env[In2] & S2]): Layer[Out2, S & S2] = To(self, that) + final infix def to[Out2, S2](that: Layer[Out2, Env[Out] & S2]): Layer[Out2, S & S2] = To(self, that) /** Combines this layer with another independent layer. * @@ -75,12 +73,12 @@ abstract class Layer[+Out, -S] extends Serializable: * The output type of the other layer * @tparam S2 * Additional effects of the other layer - * @tparam In2 - * The input type required by the second layer * @return * A new layer producing both outputs */ - final infix def using[Out2, S2, In2](that: Layer[Out2, Env[In2] & S2]): Layer[Out & Out2, S & S2] = self and (self to that) + final infix def andTo[Out2, S2](that: Layer[Out2, Env[Out] & S2]): Layer[Out & Out2, S & S2] = self and (self to that) + + final infix def using[Out2, S2](that: Layer[Out2, Env[Out] & S2]): Layer[Out & Out2, S & S2] = self andTo that end Layer @@ -93,7 +91,7 @@ object Layer: reduce(doRun(layer)) /** An empty layer that produces no output. */ - val empty: Layer[Any, Any] = FromKyo { () => TypeMap.empty } + val empty: Layer[Any, Any] = FromKyo_0 { () => TypeMap.empty } /** Creates a layer from a Kyo effect. * @@ -107,7 +105,7 @@ object Layer: * A new layer wrapping the given effect */ def apply[A: Tag, S](kyo: => A < S)(using Frame): Layer[A, S] = - FromKyo { () => + FromKyo_0 { () => kyo.map { result => TypeMap(result) } } @@ -288,34 +286,31 @@ object Layer: kyo.internal.LayerMacros.make[Target](layers*) private[kyo] object internal: - case class And[Out1, Out2, S1, S2](lhs: Layer[Out1, S1], rhs: Layer[Out2, S2]) extends Layer[Out1 & Out2, S1 & S2] - case class To[Out1, Out2, S1, S2](lhs: Layer[?, ?], rhs: Layer[?, ?]) extends Layer[Out1 & Out2, S1 & S2] - case class FromKyo[In, Out, S](kyo: () => TypeMap[Out] < (Env[In] & S))(using val tag: Tag[Out]) extends Layer[Out, S] + + case class And[Out1, Out2, S1, S2](_1: Layer[Out1, S1], _2: Layer[Out2, S2]) extends Layer[Out1 & Out2, S1 & S2] + case class To[Out1, Out2, S1, S2](_1: Layer[Out1, S1], _2: Layer[Out2, S2 & Env[Out1]]) extends Layer[Out2, S1 & S2] + case class FromKyo_0[Out, S](kyo: () => TypeMap[Out] < S)(using val tag: Tag[Out]) extends Layer[Out, S] + case class FromKyo[In, Out, S](kyo: () => TypeMap[Out] < (Env[In] & S))(using val tag: Tag[Out]) extends Layer[Out, S & Env[In]] class DoRun[Out, S] extends Serializable: private given Frame = Frame.internal - private val memo = Memo[Layer[Out, S], TypeMap[Out], S & Memo] { self => - type Expected = TypeMap[Out] < (S & Memo) - self match - case And(lhs, rhs) => - { - for - leftResult <- doRun(lhs) - rightResult <- doRun(rhs) - yield leftResult.union(rightResult) - }.asInstanceOf[Expected] - case To(lhs, rhs) => - { - for - leftResult <- doRun(lhs) - rightResult <- Env.runAll(leftResult)(doRun(rhs)) - yield rightResult - }.asInstanceOf[Expected] + private type Expected = TypeMap[Out] < (S & Memo) + private val memo = Memo[Layer[Out, S], TypeMap[Out], S & Memo] { + case And(lhs, rhs) => + for + leftResult <- doRun(lhs) + rightResult <- doRun(rhs) + yield leftResult.union(rightResult) + + case To(lhs, rhs) => + for + leftResult <- doRun(lhs) + rightResult <- Env.runAll(leftResult)(doRun(rhs)) + yield rightResult - case FromKyo(kyo) => - kyo().asInstanceOf[Expected] - end match + case FromKyo(kyo) => kyo() + case FromKyo_0(kyo) => kyo() } def apply(layer: Layer[Out, S]): TypeMap[Out] < (S & Memo) = memo(layer) end DoRun diff --git a/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala b/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala index 15bed6496..ef64a0ea2 100644 --- a/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala +++ b/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala @@ -40,7 +40,7 @@ class LayerTest extends Test: val sharkLayer = Layer.from(g => Shark(g)) val megaSharkLayer = Layer.from(s => MegaShark(s)) - val combinedLayer = guppyLayer using sharkLayer using megaSharkLayer + val combinedLayer = guppyLayer andTo sharkLayer andTo megaSharkLayer val env = Memo.run(combinedLayer.run).eval assert(env.get[Guppy].name == "Tiny Guppy") @@ -48,6 +48,52 @@ class LayerTest extends Test: assert(env.get[MegaShark].belly.belly.name == "Tiny Guppy") } + "a using b with Env2" in { + trait Dummy + case class Guppy(name: String) + case class Shark(belly: Guppy, dummy: Dummy) + + val guppyLayer: Layer[Guppy, Any] = Layer(Guppy("Tiny Guppy")) + val sharkLayer = Layer.from(Shark.apply) + val dummy: Layer[Dummy, Any] = Layer(new Dummy {}) + + val guppyAndShark: Layer[Guppy & Shark, Env[Dummy]] = guppyLayer andTo sharkLayer + + val combinedLayer = dummy andTo guppyAndShark + + val env = Memo.run(combinedLayer.run).eval + assert(env.get[Guppy].name == "Tiny Guppy") + assert(env.get[Shark].belly.name == "Tiny Guppy") + + } + + "provide should not allow circular deps" in pendingUntilFixed { + trait Dummy + case class Guppy(name: String) + case class Shark(belly: Guppy, dummy: Dummy) + + val guppyLayer: Layer[Guppy, Any] = Layer(Guppy("Tiny Guppy")) + val sharkLayer: Layer[Shark, Env[Guppy & Dummy]] = Layer.from(Shark.apply) + + typeCheckFailure("""sharkLayer provide guppyLayer""")("???") + } + + "from 2" in: + pendingUntilFixed: + case class R2(a: String, b: Int) + + // adding a type annototion to layer here is creating the issue + val layer: Layer[R2, Env[String & Int]] = Layer.from(R2.apply) + + val v: TypeMap[R2] < (Env[String & Int] & Memo) = layer.run + + val r2: R2 = v.handle(Env.run(1), Env.run("abc"), Memo.run, _.eval, _.get[R2]) + + // fatal: kyo.TypeMap of contents [TypeMap(java.lang.String -> abc, scala.Int -> 1)] missing value of type: [(scala.Int & java.lang.String)]. + assert(r2.a == "abc") + assert(r2.b == 1) + () + "In =:= Out" in { trait Value: def result: String diff --git a/kyo-zio/shared/src/test/scala/kyo/ZLayersTest.scala b/kyo-zio/shared/src/test/scala/kyo/ZLayersTest.scala index bcd5ae73f..70642d183 100644 --- a/kyo-zio/shared/src/test/scala/kyo/ZLayersTest.scala +++ b/kyo-zio/shared/src/test/scala/kyo/ZLayersTest.scala @@ -79,7 +79,7 @@ class ZLayersTest extends Test: val klayer2 = Layer(Env.use[TestService](_.getValue)) - val klayer3 = klayer1.using(klayer2) + val klayer3 = klayer1.andTo(klayer2) Env.runLayer(klayer3)(Env.use[TestService](_.getValue)).map(value => assert(value == 2)).handle(Memo.run, Scope.run) }