Hey, I am new to Scala 3 Mirror code. I was trying to learn & develope my framework. But got error when I do:
final case class AppEnv(
logger: Logger[IO],
config: Config,
userService: UserService[IO]
) derives Mirror.ProductOf
Error:
type Product in derives clause of AppEnv has no type parameters
) derives Mirror.ProductOf
I put my whole code in one page, hopefully it would be easier for people to debug it. Thanks.
import cats.effect.*
import cats.syntax.all.*
import scala.deriving.Mirror
import scala.compiletime.{erasedValue, summonInline, constValue, summonAll, summonFrom}
// ---------- Layer definition ----------
final case class Layer[F[], A](resource: Resource[F, A]) {
def map[B](f: A => B): Layer[F, B] = Layer(resource.map(f))
def flatMap[B](f: A => Layer[F, B])(using MonadCancel[F, Throwable]): Layer[F, B] =
Layer(resource.flatMap(a => f(a).resource))
}
object Layer {
def liftF[F[], A](fa: F[A]): Layer[F, A] = Layer(Resource.eval(fa))
}
// ---------- ZLayerLike abstraction ----------
trait ZLayerLike[F[_], A] {
def layer: Layer[F, A]
}
// ---------- Module pattern ----------
trait Module[F[], A] {
def make: Resource[F, A]
}
object Module {
given toZLayer[F[], A](using m: Module[F, A]): ZLayerLike[F, A] =
new ZLayerLike[F, A] {
def layer: Layer[F, A] = Layer(m.make)
}
}
// ---------- AutoLayer derivation using Scala 3 Mirror ----------
trait AutoLayer[F[], A] {
def make: Layer[F, A]
}
object AutoLayer {
def apply[F[], A](using auto: AutoLayer[F, A]): Layer[F, A] = auto.make
inline given derived[F[_], A](using n: MonadCancel[F, Throwable], m: Mirror.ProductOf[A]): AutoLayer[F, A] =
new AutoLayer[F, A] {
def make: Layer[F, A] =
makeFromTuple[F, m.MirroredElemTypes].map(m.fromProduct)
}
private inline def makeFromTuple[F[_], Elems <: Tuple](using n: MonadCancel[F, Throwable]): Layer[F, Tuple] =
reduceLayers[F](summonAllLayers[F, Elems])
private inline def summonAllLayers[F[_], T <: Tuple]: List[Layer[F, ?]] =
inline erasedValue[T] match
case _: EmptyTuple => Nil
case _: (h *: t) =>
val headLayer: Layer[F, h] = summonZLayerOrAuto[F, h]
headLayer.asInstanceOf[Layer[F, ?]] :: summonAllLayers[F, t]
private inline def summonZLayerOrAuto[F[_], A]: Layer[F, A] =
summonFrom {
case z: ZLayerLike[F, A] => z.layer
case a: AutoLayer[F, A] => a.make
}
private def reduceLayers[F[]](layers: List[Layer[F, ?]])(using n: MonadCancel[F, Throwable]): Layer[F, Tuple] =
layers match {
case Nil => Layer(Resource.pure(EmptyTuple.asInstanceOf[Tuple]))
case head :: tail =>
tail.foldLeft(head.map(Tuple1())) { (acc, next) =>
acc.flatMap(t1 => next.map(t2 => t1 ++ Tuple1(t2)))
}
}
}
// ---------- Example service modules ----------
trait Logger[F[]] {
def info(msg: String): F[Unit]
}
object Logger {
def makeResource[F[]: Sync]: Resource[F, Logger[F]] =
Resource.pure(new Logger[F] {
def info(msg: String): F[Unit] = Sync[F].delay(println(s"[info] $msg"))
})
given loggerModule[F[_]: Sync]: Module[F, Logger[F]] with
def make: Resource[F, Logger[F]] = makeResource[F]
}
final case class Config(appName: String)
object Config {
def load[F[_]: Sync]: F[Config] = Sync[F].pure(Config(âMyAppâ))
given configModule[F[_]: Sync]: Module[F, Config] with
def make: Resource[F, Config] = Resource.eval(load[F])
}
trait UserService[F[]] {
def doSomething: F[Unit]
}
object UserService {
def makeResource[F[]: Sync](config: Config): Resource[F, UserService[F]] =
Resource.pure(new UserService[F] {
def doSomething: F[Unit] = Sync[F].delay(println(âUser did somethingâ))
})
given userServiceModule[F[_]: Sync](using config: Config): Module[F, UserService[F]] with
def make: Resource[F, UserService[F]] = makeResource(config)
}
// ---------- Composite environment ----------
final case class AppEnv(
logger: Logger[IO],
config: Config,
userService: UserService[IO]
) derives Mirror.ProductOf
given AutoLayer[IO, AppEnv] = AutoLayer.derived
// ---------- Entry point ----------
object Main extends IOApp.Simple {
val appLayer: Layer[IO, AppEnv] = AutoLayer[IO, AppEnv]
override def run: IO[Unit] =
appLayer.resource.use { env =>
for {
_ â env.logger.info(s"App name: ${env.config.appName}")
_ â env.userService.doSomething
} yield ()
}
}