Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: support sum type in macwire. #236

Open
djx314 opened this issue Jun 25, 2022 · 2 comments
Open

Feature request: support sum type in macwire. #236

djx314 opened this issue Jun 25, 2022 · 2 comments

Comments

@djx314
Copy link

djx314 commented Jun 25, 2022

As the code show in gitter

trait DesuConfigModel:
  val configIO: IO[DesuConfig] = ???
end DesuConfigModel

trait AppConfig(config: DesuConfig):
end AppConfig

trait DoobieDB(config: DesuConfig):
  private val dsConfigIO = IO(config.mysqlDesuQuillDB.dataSource)
  val transactor: Resource[IO, HikariTransactor[IO]] = ???
end DoobieDB

trait FileFinder(appConfig: AppConfig, xa: Transactor[IO]):
  // code
end FileFinder

trait AppRoutes(fileFinder: FileFinder, appConfig: AppConfig):
  // code
end AppRoutes

val configModel = wire[DesuConfigModel]
val appRoutes: Resource[IO, AppRoutes] = for
  desuConfig  <- Resource.eval(configModel.configIO)
  appConfig    = wire[AppConfig]
  doobieDB     = wire[DoobieDB]
  xa                <- doobieDB.transactor
yield
  val fileFinder = wire[FileFinder]
  wire[AppRoutes]

Macwire can support wire in F[_] like distage. Since implicit value and constructor value is fetched by type. You can declare a implicit value like

implicit val implicitVarName = constructorVarName

Here request a feature that support sum type in macwire like zio.ZEnvironment. Then we can support something like distage's Module include(simple version) in Scala2 and Scala3.(distage doc)

Here is the code also in gitter

class DesuConfigModelImpl extends DesuConfigModel

class AppConfigImpl(using DesuConfig) extends AppConfig(summon)

class DoobieDBImpl(using DesuConfig) extends DoobieDB(summon)

class FileFinderImpl(using AppConfig, Transactor[IO]) extends FileFinder(summon, summon)

class AppRoutesImpl(using FileFinder, AppConfig) extends AppRoutes(summon, summon)

import zio.{IO as _, *}

object MainAppInjected:

  type ProjectEnvModule1 = DesuConfig & AppConfig & Transactor[IO]

  given [T: Tag, S <: T](using ZEnvironment[S]): T = summon[ZEnvironment[S]].get

  val envResource: Resource[IO, ZEnvironment[ProjectEnvModule1]] = for
    given DesuConfig     <- Resource.eval((new DesuConfigModelImpl).configIO)
    given AppConfig       = new AppConfigImpl
    doobieDB                 = new DoobieDBImpl
    given Transactor[IO] <- doobieDB.transactor
  yield ZEnvironment(implicitly[DesuConfig], implicitly[Transactor[IO]], implicitly[AppConfig])

  val appRoutes: Resource[IO, AppRoutes] = for
    given ZEnvironment[ProjectEnvModule1] <- envResource // distage include(simple version)
  yield
    given FileFinder = new FileFinderImpl
    new AppRoutesImpl

end MainAppInjected

It circuitously performs the macwire function I imagined use Scala3 and zio.ZEnvironment.

@djx314
Copy link
Author

djx314 commented Jun 27, 2022

And I find that macwire perhaps can work fine with cats-effect-cps

Here's the new version with inject part(also use other way to implement, thanks for the powerful expressive ability in Scala3)

object MainAppInjected:

  object module1 extends InjectedModule1
  import module1.{env as env1, Env as Env1}

  val appRoutes: Resource[IO, AppRoutes] = async[Resource[IO, *]] {
    given ZEnvironment[Env1] = env1.await
    given FileService        = new FileServiceImpl
    given FileFinder         = new FileFinderImpl
    new AppRoutesImpl
  }

end MainAppInjected

trait InjectedModule1:

  type Env = DesuConfig & AppConfig & Transactor[IO]

  val env: Resource[IO, ZEnvironment[Env]] = async[Resource[IO, *]] {
    val configModel      = new DesuConfigModelImpl
    given DesuConfig     = Resource.eval(configModel.configIO).await
    given AppConfig      = new AppConfigImpl
    val doobieDB         = new DoobieDBImpl
    given Transactor[IO] = doobieDB.transactor.await
    ZEnvironment(implicitly[DesuConfig], implicitly[Transactor[IO]], implicitly[AppConfig])
  }

end InjectedModule1

given [ModelTag: Tag, S <: ModelTag](using ZEnvironment[S]): ModelTag = summon[ZEnvironment[S]].get

And the cps lib seems that can work with zio and scala future.

@djx314
Copy link
Author

djx314 commented Sep 12, 2024

@mbore finally I use macwire in a simple way. No magic, compat in Scala 2 and Scala 3. If someone use zio, just ZIO.identity.

https://scastie.scala-lang.org/djx314/r5hrGExzTKuUT1bRBGx6dg/64

case class DesuConfig(foo: Long, bar: String, mysqlDesuQuillDB: DataSource)
case class DataSource(dataSource: String)

class DesuConfigModel:
  val configIO: IO[DesuConfig] = ???
end DesuConfigModel

class AppConfig(config: DesuConfig):
end AppConfig

class DoobieDB(config: DesuConfig):
  private val dsConfigIO = IO(config.mysqlDesuQuillDB.dataSource)
  val transactor: Resource[IO, HikariTransactor[IO]] = ???
end DoobieDB

class FileFinder(appConfig: AppConfig, xa: Transactor[IO]):
// code
end FileFinder

class AppRoutes(fileFinder: FileFinder, appConfig: AppConfig):
// code
end AppRoutes

object MainAppInjected:

  val envResource1: Resource[IO, AppRoutes] =
    Resource.eval(wire[DesuConfigModel].configIO).flatMap {
      (desuConfig: DesuConfig) =>
        Resource.pure(wire[AppConfig]).flatMap { (appConf: AppConfig) =>
          Resource.pure(wire[DoobieDB]).flatMap { (doobieDB: DoobieDB) =>
            doobieDB.transactor.flatMap { (transactor: Transactor[IO]) =>
              Resource.pure(wire[FileFinder]).map { (fileFinder: FileFinder) =>
                wire[AppRoutes]
              }
            }
          }
        }
    }

  val envResource2: Resource[IO, AppRoutes] = for
    desuConfig: DesuConfig <- Resource.eval(wire[DesuConfigModel].configIO)
    appConf: AppConfig <- Resource.pure(wire[AppConfig])
    doobieDB: DoobieDB <- Resource.pure(wire[DoobieDB])
    transactor: Transactor[IO] <- doobieDB.transactor
    fileFinder: FileFinder <- Resource.pure(wire[FileFinder])
  yield wire[AppRoutes]

end MainAppInjected

The problem that in macwire is, we can't use these code.

// === Error 1
for {
  objectA <- Resource.pure(wire[ClassA]) // Right
  objectB = wire[ClassB] // Wrong, objectB can not be wired.
} yield wire[ClassC]

// === workaround 1
for {
  objectA <- Resource.pure(wire[ClassA])
  objectB <- Rerource.pure(wire[ClassB])
} yield wire[ClassC]

// === Error 2
Resource.pure(wire[ClassA]).flatMap { (objectA: ClassA) => // Right

  val objectD: ClassD = wire[ClassD] // Wrong, objectD can not be wired.

  Resource.pure(wire[ClassB]).map { (objectB: ClassB) => // Right
    wire[ClassC]
  }
}

// workaround 2
Resource.pure(wire[ClassA]).flatMap { (objectA: ClassA) =>
  Resource.pure(wire[ClassD]).flatMap { (objectD: ClassD) =>
    Resource.pure(wire[ClassB]).map { (objectB: ClassB) =>
      wire[ClassC]
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants