From 95dd0b69aa5852adee784fed6266229e568868ec Mon Sep 17 00:00:00 2001 From: Jonathan Winandy Date: Sun, 21 Sep 2025 14:38:25 +0200 Subject: [PATCH 01/11] fix: replace `using` with `provide` in Layer API and update tests, and fix type arguments for `provide` --- .../shared/src/main/scala/kyo/Layer.scala | 5 ++- .../shared/src/test/scala/kyo/LayerTest.scala | 39 ++++++++++++++++++- .../src/test/scala/kyo/ZLayersTest.scala | 2 +- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/kyo-prelude/shared/src/main/scala/kyo/Layer.scala b/kyo-prelude/shared/src/main/scala/kyo/Layer.scala index 70ca8e54d..7d6b611d5 100644 --- a/kyo-prelude/shared/src/main/scala/kyo/Layer.scala +++ b/kyo-prelude/shared/src/main/scala/kyo/Layer.scala @@ -80,7 +80,10 @@ abstract class Layer[+Out, -S] extends Serializable: * @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 provide[Out2, S2](that: Layer[Out2, Env[Out] & S2]): Layer[Out & Out2, S & S2] = self and (self to that) + + @deprecated("Use `provide` instead, which is more consistent considering the argument order.") + final infix def using[Out2, S2](that: Layer[Out2, S2]): Layer[Out & Out2, S & S2] = self provide that end Layer diff --git a/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala b/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala index 4e86755eb..5616ae55a 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 provide sharkLayer provide megaSharkLayer val env = Memo.run(combinedLayer.run).eval assert(env.get[Guppy].name == "Tiny Guppy") @@ -48,6 +48,43 @@ 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 guppyAndShart: Layer[Guppy & Shark, Env[Dummy]] = guppyLayer provide dummy provide sharkLayer + // note: circular deps, not usable + val _: Layer[Shark & Guppy, Env[Guppy & Dummy]] = sharkLayer using guppyLayer + + val combinedLayer = dummy provide guppyAndShart + + val env = Memo.run(combinedLayer.run).eval + assert(env.get[Guppy].name == "Tiny Guppy") + assert(env.get[Shark].belly.name == "Tiny Guppy") + + } + + "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..0fb967244 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.provide(klayer2) Env.runLayer(klayer3)(Env.use[TestService](_.getValue)).map(value => assert(value == 2)).handle(Memo.run, Scope.run) } From a2e68ee752caa5e6ddb926afb87f3faa32f3c9e9 Mon Sep 17 00:00:00 2001 From: Jonathan Winandy Date: Sun, 21 Sep 2025 14:41:55 +0200 Subject: [PATCH 02/11] fix: test --- kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala b/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala index 5616ae55a..19e8ed82c 100644 --- a/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala +++ b/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala @@ -57,7 +57,7 @@ class LayerTest extends Test: val sharkLayer = Layer.from(Shark.apply) val dummy: Layer[Dummy, Any] = Layer(new Dummy {}) - val guppyAndShart: Layer[Guppy & Shark, Env[Dummy]] = guppyLayer provide dummy provide sharkLayer + val guppyAndShart: Layer[Guppy & Shark, Env[Dummy]] = guppyLayer provide sharkLayer // note: circular deps, not usable val _: Layer[Shark & Guppy, Env[Guppy & Dummy]] = sharkLayer using guppyLayer From 44f835faab8d88c9489d040d42e04e66dc253b3a Mon Sep 17 00:00:00 2001 From: Jonathan Winandy Date: Sun, 21 Sep 2025 17:56:38 +0200 Subject: [PATCH 03/11] fix: replace `using` with `provide` in README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0649f7b4b..2a9f543e1 100644 --- a/README.md +++ b/README.md @@ -828,7 +828,7 @@ The `Layer` type provides instance methods for manually composing layers: 1. `to`: Combines two layers sequentially, where the output of the first layer is used as input for the second layer. 2. `and`: Combines two layers in parallel, producing a layer that provides both outputs. -3. `using`: Combines a layer with another layer that depends on its output, similar to `to` but keeps both outputs. +3. `provide`: Combines a layer with another layer that depends on its output, similar to `to` but keeps both outputs. Here's an example that demonstrates the differences between these methods: @@ -871,12 +871,12 @@ val dbAndEmail: Layer[Database & EmailService, Sync] = dbLayer.and(emailServiceLayer) // Example of `using`: Similar to `to`, but keeps both Database and UserService -val dbUsingUserService: Layer[Database & UserService, Sync] = - dbLayer.using(userServiceLayer) +val userServiceUsingDb: Layer[Database & UserService, Sync] = + dbLayer.provide(userServiceLayer) // Complex composition val fullAppLayer: Layer[Database & UserService & EmailService, Sync] = - dbLayer.using(userServiceLayer).and(emailServiceLayer) + dbLayer.provide(userServiceLayer).and(emailServiceLayer) // Use the full app layer val computation: Unit < (Env[Database] & Env[UserService] & Env[EmailService] & Sync) = From bba926d4ac911eb772efb2a4acf8e571bbaa2776 Mon Sep 17 00:00:00 2001 From: Jonathan Winandy Date: Sun, 21 Sep 2025 20:50:35 +0200 Subject: [PATCH 04/11] fix: adjust `to` method and internal `To` case class in Layer API --- .../shared/src/main/scala/kyo/Layer.scala | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/kyo-prelude/shared/src/main/scala/kyo/Layer.scala b/kyo-prelude/shared/src/main/scala/kyo/Layer.scala index 7d6b611d5..ba7c33ac8 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, In2](that: Layer[Out2, Env[Out] & S2]): Layer[Out2, S & S2] = To(self, that) /** Combines this layer with another independent layer. * @@ -292,33 +290,27 @@ object Layer: 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 To[Out1, Out2, S1, S2](lhs: Layer[Out1, S1], rhs: Layer[Out2, S2 & Env[Out1]]) extends Layer[Out2, S1 & S2] case class FromKyo[In, Out, S](kyo: () => TypeMap[Out] < (Env[In] & S))(using val tag: Tag[Out]) extends Layer[Out, S] 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().asInstanceOf[Expected] } def apply(layer: Layer[Out, S]): TypeMap[Out] < (S & Memo) = memo(layer) end DoRun From 39be2cba260d706c693a9f2096ec2d8f147b7479 Mon Sep 17 00:00:00 2001 From: Jonathan Winandy Date: Fri, 3 Oct 2025 20:19:08 +0200 Subject: [PATCH 05/11] fix: optimize `union` in `TypeMap` and adjust `provide` implementation in Layer API --- .../shared/src/main/scala/kyo/TypeMap.scala | 4 +++- .../shared/src/main/scala/kyo/Layer.scala | 20 +++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) 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 ba7c33ac8..809e272e6 100644 --- a/kyo-prelude/shared/src/main/scala/kyo/Layer.scala +++ b/kyo-prelude/shared/src/main/scala/kyo/Layer.scala @@ -73,12 +73,10 @@ 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 provide[Out2, S2](that: Layer[Out2, Env[Out] & S2]): Layer[Out & Out2, S & S2] = self and (self to that) + final infix def provide[Out2, S2](that: Layer[Out2, Env[Out] & S2]): Layer[Out & Out2, S & S2] = And(self, that) @deprecated("Use `provide` instead, which is more consistent considering the argument order.") final infix def using[Out2, S2](that: Layer[Out2, S2]): Layer[Out & Out2, S & S2] = self provide that @@ -94,7 +92,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. * @@ -108,7 +106,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) } } @@ -289,9 +287,10 @@ 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[Out1, S1], rhs: Layer[Out2, S2 & Env[Out1]]) extends Layer[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](first: Layer[Out1, S1], second: Layer[Out2, S2 & Env[Out1]]) extends Layer[Out1 & Out2, S1 & S2] + case class To[Out1, Out2, S1, S2](first: Layer[Out1, S1], second: 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 @@ -301,7 +300,7 @@ object Layer: case And(lhs, rhs) => for leftResult <- doRun(lhs) - rightResult <- doRun(rhs) + rightResult <- Env.runAll(leftResult)(doRun(rhs)) yield leftResult.union(rightResult) case To(lhs, rhs) => @@ -310,7 +309,8 @@ object Layer: rightResult <- Env.runAll(leftResult)(doRun(rhs)) yield rightResult - case FromKyo(kyo) => kyo().asInstanceOf[Expected] + case FromKyo(kyo) => kyo() + case FromKyo_0(kyo) => kyo() } def apply(layer: Layer[Out, S]): TypeMap[Out] < (S & Memo) = memo(layer) end DoRun From 4e45d4476bfb5c7588506440db57e765476c83ac Mon Sep 17 00:00:00 2001 From: Jonathan Winandy Date: Sat, 4 Oct 2025 00:03:37 +0200 Subject: [PATCH 06/11] fix: add test to prevent circular dependencies in Layer API's `provide` method --- kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala b/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala index 19e8ed82c..eef6ab315 100644 --- a/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala +++ b/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala @@ -69,6 +69,17 @@ class LayerTest extends Test: } + "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) From 50e3f2c204bbb781ec4e652f4b193fc667ee9eff Mon Sep 17 00:00:00 2001 From: Jonathan Winandy Date: Sat, 4 Oct 2025 01:55:37 +0200 Subject: [PATCH 07/11] clean test --- kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala b/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala index eef6ab315..b1b0c33be 100644 --- a/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala +++ b/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala @@ -58,8 +58,6 @@ class LayerTest extends Test: val dummy: Layer[Dummy, Any] = Layer(new Dummy {}) val guppyAndShart: Layer[Guppy & Shark, Env[Dummy]] = guppyLayer provide sharkLayer - // note: circular deps, not usable - val _: Layer[Shark & Guppy, Env[Guppy & Dummy]] = sharkLayer using guppyLayer val combinedLayer = dummy provide guppyAndShart From 0a1453cd92d74721095e71e28a08faa7eedae521 Mon Sep 17 00:00:00 2001 From: Jonathan Winandy Date: Mon, 20 Oct 2025 00:11:14 +0200 Subject: [PATCH 08/11] fix: replace `provide` with `andTo` in Layer API, update tests, and adjust README accordingly --- README.md | 2 +- .../shared/src/main/scala/kyo/Layer.scala | 20 +++++++++++++------ .../shared/src/test/scala/kyo/LayerTest.scala | 6 +++--- .../src/test/scala/kyo/ZLayersTest.scala | 2 +- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2a9f543e1..5c3f06fe9 100644 --- a/README.md +++ b/README.md @@ -828,7 +828,7 @@ The `Layer` type provides instance methods for manually composing layers: 1. `to`: Combines two layers sequentially, where the output of the first layer is used as input for the second layer. 2. `and`: Combines two layers in parallel, producing a layer that provides both outputs. -3. `provide`: Combines a layer with another layer that depends on its output, similar to `to` but keeps both outputs. +3. `andTo`: Combines a layer with another layer that depends on its output, similar to `to` but keeps both outputs like `and`. Here's an example that demonstrates the differences between these methods: diff --git a/kyo-prelude/shared/src/main/scala/kyo/Layer.scala b/kyo-prelude/shared/src/main/scala/kyo/Layer.scala index 809e272e6..b9c5be7bc 100644 --- a/kyo-prelude/shared/src/main/scala/kyo/Layer.scala +++ b/kyo-prelude/shared/src/main/scala/kyo/Layer.scala @@ -50,7 +50,7 @@ abstract class Layer[+Out, -S] extends Serializable: * @return * A new layer representing the composition of both layers */ - final infix def to[Out2, S2, In2](that: Layer[Out2, Env[Out] & 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. * @@ -76,10 +76,10 @@ abstract class Layer[+Out, -S] extends Serializable: * @return * A new layer producing both outputs */ - final infix def provide[Out2, S2](that: Layer[Out2, Env[Out] & S2]): Layer[Out & Out2, S & S2] = And(self, that) + final infix def andTo[Out2, S2](that: Layer[Out2, Env[Out] & S2]): Layer[Out & Out2, S & S2] = AndTo(self, that) - @deprecated("Use `provide` instead, which is more consistent considering the argument order.") - final infix def using[Out2, S2](that: Layer[Out2, S2]): Layer[Out & Out2, S & S2] = self provide that + @deprecated("Use `andTo` instead, which is more consistent considering the argument order.") + final infix def using[Out2, S2](that: Layer[Out2, S2]): Layer[Out & Out2, S & S2] = self andTo that end Layer @@ -287,8 +287,10 @@ object Layer: kyo.internal.LayerMacros.make[Target](layers*) private[kyo] object internal: - case class And[Out1, Out2, S1, S2](first: Layer[Out1, S1], second: Layer[Out2, S2 & Env[Out1]]) extends Layer[Out1 & Out2, S1 & S2] - case class To[Out1, Out2, S1, S2](first: Layer[Out1, S1], second: Layer[Out2, S2 & Env[Out1]]) extends Layer[Out2, S1 & S2] + + 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 AndTo[Out1, Out2, S1, S2](_1: Layer[Out1, S1], _2: Layer[Out2, S2 & Env[Out1]]) extends Layer[Out1 & 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]] @@ -298,6 +300,12 @@ object Layer: 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 AndTo(lhs, rhs) => for leftResult <- doRun(lhs) rightResult <- Env.runAll(leftResult)(doRun(rhs)) diff --git a/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala b/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala index b1b0c33be..e80b074f9 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 provide sharkLayer provide megaSharkLayer + val combinedLayer = guppyLayer andTo sharkLayer andTo megaSharkLayer val env = Memo.run(combinedLayer.run).eval assert(env.get[Guppy].name == "Tiny Guppy") @@ -57,9 +57,9 @@ class LayerTest extends Test: val sharkLayer = Layer.from(Shark.apply) val dummy: Layer[Dummy, Any] = Layer(new Dummy {}) - val guppyAndShart: Layer[Guppy & Shark, Env[Dummy]] = guppyLayer provide sharkLayer + val guppyAndShart: Layer[Guppy & Shark, Env[Dummy]] = guppyLayer andTo sharkLayer - val combinedLayer = dummy provide guppyAndShart + val combinedLayer = dummy andTo guppyAndShart val env = Memo.run(combinedLayer.run).eval assert(env.get[Guppy].name == "Tiny Guppy") diff --git a/kyo-zio/shared/src/test/scala/kyo/ZLayersTest.scala b/kyo-zio/shared/src/test/scala/kyo/ZLayersTest.scala index 0fb967244..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.provide(klayer2) + val klayer3 = klayer1.andTo(klayer2) Env.runLayer(klayer3)(Env.use[TestService](_.getValue)).map(value => assert(value == 2)).handle(Memo.run, Scope.run) } From dc4622b25abaceb66594e2105b87d8825c74b5cd Mon Sep 17 00:00:00 2001 From: Jonathan Winandy Date: Tue, 21 Oct 2025 02:11:53 +0200 Subject: [PATCH 09/11] fix: replace `provide` with `andTo` in README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5c3f06fe9..21d018be8 100644 --- a/README.md +++ b/README.md @@ -870,13 +870,13 @@ val dbToUserService: Layer[UserService, Sync] = val dbAndEmail: Layer[Database & EmailService, Sync] = dbLayer.and(emailServiceLayer) -// Example of `using`: Similar to `to`, but keeps both Database and UserService +// Example of `andTo`: Similar to `to`, but keeps both Database and UserService val userServiceUsingDb: Layer[Database & UserService, Sync] = - dbLayer.provide(userServiceLayer) + dbLayer.andTo(userServiceLayer) // Complex composition val fullAppLayer: Layer[Database & UserService & EmailService, Sync] = - dbLayer.provide(userServiceLayer).and(emailServiceLayer) + dbLayer.andTo(userServiceLayer).and(emailServiceLayer) // Use the full app layer val computation: Unit < (Env[Database] & Env[UserService] & Env[EmailService] & Sync) = From 2a543b7fcfd2c7df27d836b28b6671742edc91f6 Mon Sep 17 00:00:00 2001 From: Jonathan Winandy Date: Thu, 29 Jan 2026 05:27:43 +0100 Subject: [PATCH 10/11] Refactor Layer methods for consistency and clarity - Updated `andTo` method implementation to use a more consistent syntax. - Replaced deprecated `using` method with a direct call to `andTo`. - Removed unused `AndTo` case class from the `Layer` object. - Adjusted test cases to reflect the changes in method usage. --- kyo-prelude/shared/src/main/scala/kyo/Layer.scala | 14 +++----------- .../shared/src/test/scala/kyo/LayerTest.scala | 4 ++-- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/kyo-prelude/shared/src/main/scala/kyo/Layer.scala b/kyo-prelude/shared/src/main/scala/kyo/Layer.scala index b9c5be7bc..339200a9e 100644 --- a/kyo-prelude/shared/src/main/scala/kyo/Layer.scala +++ b/kyo-prelude/shared/src/main/scala/kyo/Layer.scala @@ -76,10 +76,9 @@ abstract class Layer[+Out, -S] extends Serializable: * @return * A new layer producing both outputs */ - final infix def andTo[Out2, S2](that: Layer[Out2, Env[Out] & S2]): Layer[Out & Out2, S & S2] = AndTo(self, that) + final infix def andTo[Out2, S2](that: Layer[Out2, Env[Out] & S2]): Layer[Out & Out2, S & S2] = self and (self to that) - @deprecated("Use `andTo` instead, which is more consistent considering the argument order.") - final infix def using[Out2, S2](that: Layer[Out2, S2]): Layer[Out & Out2, S & S2] = self andTo that + final infix def using[Out2, S2](that: Layer[Out2, Env[Out] & S2]): Layer[Out & Out2, S & S2] = self andTo that end Layer @@ -290,7 +289,6 @@ object Layer: 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 AndTo[Out1, Out2, S1, S2](_1: Layer[Out1, S1], _2: Layer[Out2, S2 & Env[Out1]]) extends Layer[Out1 & 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]] @@ -302,13 +300,7 @@ object Layer: case And(lhs, rhs) => for leftResult <- doRun(lhs) - rightResult <- (doRun(rhs)) - yield leftResult.union(rightResult) - - case AndTo(lhs, rhs) => - for - leftResult <- doRun(lhs) - rightResult <- Env.runAll(leftResult)(doRun(rhs)) + rightResult <- doRun(rhs) yield leftResult.union(rightResult) case To(lhs, rhs) => diff --git a/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala b/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala index e80b074f9..a1578f305 100644 --- a/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala +++ b/kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala @@ -57,9 +57,9 @@ class LayerTest extends Test: val sharkLayer = Layer.from(Shark.apply) val dummy: Layer[Dummy, Any] = Layer(new Dummy {}) - val guppyAndShart: Layer[Guppy & Shark, Env[Dummy]] = guppyLayer andTo sharkLayer + val guppyAndShark: Layer[Guppy & Shark, Env[Dummy]] = guppyLayer andTo sharkLayer - val combinedLayer = dummy andTo guppyAndShart + val combinedLayer = dummy andTo guppyAndShark val env = Memo.run(combinedLayer.run).eval assert(env.get[Guppy].name == "Tiny Guppy") From 6728db015e81ee41b4f7d9d88b5d6e3f0d3d2ae7 Mon Sep 17 00:00:00 2001 From: Jonathan Winandy Date: Thu, 29 Jan 2026 07:57:38 +0100 Subject: [PATCH 11/11] Update README to rename `andTo` method to `using` for clarity - Changed references of `andTo` to `using` in the documentation. - Updated example code to reflect the new method name and its usage. - Ensured consistency in method descriptions for better understanding. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 21d018be8..0649f7b4b 100644 --- a/README.md +++ b/README.md @@ -828,7 +828,7 @@ The `Layer` type provides instance methods for manually composing layers: 1. `to`: Combines two layers sequentially, where the output of the first layer is used as input for the second layer. 2. `and`: Combines two layers in parallel, producing a layer that provides both outputs. -3. `andTo`: Combines a layer with another layer that depends on its output, similar to `to` but keeps both outputs like `and`. +3. `using`: Combines a layer with another layer that depends on its output, similar to `to` but keeps both outputs. Here's an example that demonstrates the differences between these methods: @@ -870,13 +870,13 @@ val dbToUserService: Layer[UserService, Sync] = val dbAndEmail: Layer[Database & EmailService, Sync] = dbLayer.and(emailServiceLayer) -// Example of `andTo`: Similar to `to`, but keeps both Database and UserService -val userServiceUsingDb: Layer[Database & UserService, Sync] = - dbLayer.andTo(userServiceLayer) +// Example of `using`: Similar to `to`, but keeps both Database and UserService +val dbUsingUserService: Layer[Database & UserService, Sync] = + dbLayer.using(userServiceLayer) // Complex composition val fullAppLayer: Layer[Database & UserService & EmailService, Sync] = - dbLayer.andTo(userServiceLayer).and(emailServiceLayer) + dbLayer.using(userServiceLayer).and(emailServiceLayer) // Use the full app layer val computation: Unit < (Env[Database] & Env[UserService] & Env[EmailService] & Sync) =