Skip to content
Open
Show file tree
Hide file tree
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
4 changes: 3 additions & 1 deletion kyo-data/shared/src/main/scala/kyo/TypeMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
57 changes: 26 additions & 31 deletions kyo-prelude/shared/src/main/scala/kyo/Layer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@deprecated?


end Layer

Expand All @@ -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.
*
Expand All @@ -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) }
}

Expand Down Expand Up @@ -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()
Comment on lines +312 to +313
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these will be materialized to the same expected type - I don't think we need a distinct variant. Can we keep it as just FromKyo?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be nice, but Env[Nothing] will not be helpful.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just cast. There's no difference in the underlying representation due to type widening.

}
def apply(layer: Layer[Out, S]): TypeMap[Out] < (S & Memo) = memo(layer)
end DoRun
Expand Down
48 changes: 47 additions & 1 deletion kyo-prelude/shared/src/test/scala/kyo/LayerTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,60 @@ 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")
assert(env.get[Shark].belly.name == "Tiny Guppy")
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
Expand Down
2 changes: 1 addition & 1 deletion kyo-zio/shared/src/test/scala/kyo/ZLayersTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Loading