I have two database connections that I want to be able to separate. I found this post that describes how to implement Shapeless style tagged types in Scala 3. However, when I try to create a ZLayer
with a tagged value, I can never satisfy the implicits. Tried every combination I could think of with typed methods and/or passing type parameters - same result all the time.
For the sake of simplicity, I'll just give example code trying to tag String
instead.
object TaggedTypes {
opaque type Tagged[+V, +Tag] = Any
type @@[+V, +Tag] = V & Tagged[V, Tag]
def tag[Tag]: [V] => V => V @@ Tag =
[V] => (v: V) => v
}
import TaggedTypes.*
trait Tag1
trait Tag2
val layer1: ULayer[String @@ Tag1] = ZLayer.succeed(tag[Tag1]("string1"))
val layer2: ULayer[String @@ Tag2] = ZLayer.succeed(tag[Tag2]("string2"))
val program: UIO[Unit] = for {
string1 <- ZIO.service[String @@ Tag1]
string2 <- ZIO.service[String @@ Tag2]
_ <- zio.Console.printLine(s"This should have value 'string1': ${string1}")
_ <- zio.Console.printLine(s"This should have value 'string2': ${string2}")
} yield ()
If program
would be run, I would expect values to match as in console log statements.
Instead, compilation fails with:
could not find implicit value for izumi.reflect.Tag[(String & TaggedTypes.Tagged[String, Tag1])].
Did you forget to put on a Tag, TagK or TagKK context bound on one of the parameters in (String & TaggedTypes.Tagged[String, Tag1])? e.g. def x[T: Tag, F[_]: TagK] = ....
I found: zio.Tag.materialize[ (String & TaggedTypes.Tagged[String, Tag1]) ]
But method materialize in trait TagVersionSpecific does not match type zio.Tag[(String & TaggedTypes.Tagged[String, Tag1]) ].
zio
is on version 2.0.13
, izumi-reflect
is on version 2.3.7
To do what you are trying to do here isn't really the way that ZIO wants you to handle this. Its mostly possible except you need to have a type tag for each connection (String in the example) that is unique, which I'm not sure how to do.
The code below will always print string2
because TaggedString[T]
always returns the same Tag and thus the write to the environment Map is overwritten.
import izumi.reflect.dottyreflection.ReflectionUtil.reflectiveUncheckedNonOverloadedSelectable
import zio.{Tag, UIO, ULayer, ZEnvironment, ZIO, ZIOAppDefault, ZLayer}
import java.io.IOException
opaque type Tagged[+V, +Tag] = Any
type @@[+V, +Tag] = V & Tagged[V, Tag]
def tag[Tag]: [V] => V => V @@ Tag = [V] => (v: V) => v
trait Tag1
trait Tag2
object TagExample extends ZIOAppDefault {
override def run: ZIO[Any, IOException, Unit] = {
// The problem is to express one of these that is unique for each Tag
given TaggedString[T]: Tag[String @@ T] = Tag[String].asInstanceOf[Tag[String @@ T]]
val a: String @@ Tag1 = tag[Tag1]("string1")
val b: String @@ Tag2 = tag[Tag2]("string2")
val layer1: ULayer[String @@ Tag1] = ZLayer.succeed[String @@ Tag1](a)
val layer2: ULayer[String @@ Tag2] = ZLayer.succeed[String @@ Tag2](b)
val program: ZIO[String @@ Tag1 & String @@ Tag2, IOException, Unit] = for {
string1 <- ZIO.service[String @@ Tag1]
string2 <- ZIO.service[String @@ Tag2]
_ <- zio.Console.printLine(s"This should have value 'string1': ${string1}")
_ <- zio.Console.printLine(s"This should have value 'string2': ${string2}")
} yield ()
program.provideLayer(layer1 ++ layer2)
}
}
However, its MUCH simpler to just wrap the connections in a case class to give them separate types.
This example works properly:
import zio.{UIO, ULayer, ZEnvironment, ZIO, ZIOAppDefault, ZLayer}
import java.io.IOException
case class String1(value: String)
case class String2(value: String)
object TagExample extends ZIOAppDefault {
override def run: ZIO[Any, IOException, Unit] = {
val layer1: ULayer[String1] = ZLayer.succeed(String1("string1"))
val layer2: ULayer[String2] = ZLayer.succeed(String2("string2"))
val program: ZIO[String1 & String2, IOException, Unit] = for {
string1 <- ZIO.service[String1]
string2 <- ZIO.service[String2]
_ <- zio.Console.printLine(s"This should have value 'string1': ${string1.value}")
_ <- zio.Console.printLine(s"This should have value 'string2': ${string2.value}")
} yield ()
program.provideLayer(layer1 ++ layer2)
}
}