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

Is there any complete example of Distage usage that doesn't involve ZIO? #2043

Open
OOPMan opened this issue Dec 2, 2023 · 7 comments
Open

Comments

@OOPMan
Copy link

OOPMan commented Dec 2, 2023

Hi there, I'm curious as to whether there is any example code that demonstrates using Distage without ZIO?

All the code in the example application and on the site involve ZIO but according to the page the framework is actually agnostic about this kind of thing?

I played with some code on my side and got a basic plugin injection system working with minimal issues but with the caveat that whenever I want to use the injector I have to do an unsafeGet. Obviously littering my code with stuff labelled unsafe makes me feel...well...unsafe, so I was wondering if there is another accepted way to do things "safely" in a basic Scala application that is not using ZIO, Cats or any other similar frameworks.

@neko-kai
Copy link
Member

neko-kai commented Dec 2, 2023

@OOPMan Not first-party, but there's an example project using cats-effect (and tofu) + distage - https://github.com/terjokhin/helpful - it might be outdated though. It might be a bit too large a project to use as an example, but the https://github.com/BlueBrain/nexus/ uses distage with cats-effect.
Actually, if you look closely, many examples on the site do use cats-effect instead of ZIO, and the distage-example also uses cats-effect typeclasses and doobie (although it does run on ZIO)

For actually running on cats IO instead of ZIO, there aren't many changes you have to make compared to distage-example project:

  • when using distage-framework, use RoleAppMain.LauncherCats instead of RoleAppMain.LauncherBIO,
  • when using distage-teskit use Spec1[IO] instead of SpecZIO,
  • when using distage-core, use Injector[IO] instead of Injector[zio.Task].

As for unsafeGet - you should be able to use the .use method instead, most of the time. But, if you're stuck, you can use .toCats[IO] method on the Lifecycle value you get from Injector.produce to get a cats.effect.Resource – you can then use the Resource value as usual with cats.effect.IOApp or otherwise.

EDIT: If you're not using either ZIO or Cats Effect, then the prescription is simpler:

  • when using distage-framework, use RoleAppMain.LauncherIdentity instead of RoleAppMain.LauncherBIO,
  • when using distage-teskit use SpecIdentity (or Spec1[Identity]) instead of SpecZIO,
  • when using distage-core, use Injector() (same as Injector[Identity]()) instead of Injector[zio.Task].

You can find Identity in izumi.fundamentals.platform.functional.Identity

@OOPMan
Copy link
Author

OOPMan commented Dec 2, 2023

@neko-kai I think maybe you misunderstood. I'm trying to use distage without using ZIO, Cats or anything like that.

As mentioned, I can get stuff with unsafeGet or unsafeAllocate and that's nice but in this scenario if I try to make use of use I get errors messages like so:

[error] 36 |    }
[error]    |     ^
[error]    |No given instance of type izumi.functional.quasi.QuasiPrimitives[[X0] =>> Object] was found for parameter F of method use in class SyntaxUse.
[error]    |I found:
[error]    |
[error]    |    izumi.functional.quasi.QuasiPrimitives.fromCats[[X0] =>> Object, Sync](
[error]    |      /* missing */summon[izumi.fundamentals.orphans.cats.effect.kernel.Sync[Sync]]
[error]    |        ,
[error]    |    ???)
[error]    |
[error]    |But no implicit values were found that match type izumi.fundamentals.orphans.cats.effect.kernel.Sync[Sync].
[error]    |
[error]    |The following import might make progress towards fixing the problem:
[error]    |
[error]    |  import izumi.functional.quasi.QuasiIO.fromCats
[error]    |
[error] one error found

Do I HAVE to use something like ZIO or Cats to make this work?

From what I saw in the documentation it seemed like this wasn't necessarily the case?

@OOPMan
Copy link
Author

OOPMan commented Dec 2, 2023

Sorry, meant to include some sample code as well

import distage._

trait A:
  def run(s: String) = s + " " + s

class anA extends A

trait B:
  val a: A
  def run(s: String) = s + a.run(s)

class aB(override val a: A) extends B

val testModule = new ModuleDef:
  make[A].from[anA]
  make[B].from[aB]

object Test:
  val injector = Injector()

  def main(args: Array[String]): Unit =
   // This works
    val (a, b) = injector.produceGet[B](testModule).unsafeAllocate()
    println(a.run("test"))
    b.apply()
    // This doesn't, failing when I attempt to make use of the .use pathway due to missing implicit
    val p = injector.plan(testModule, Activation.empty, Roots.target[B]).getOrThrow()
    val r = injector.produce(p)
    r.use {
      os => os.get[B].run("zing")
    }

@neko-kai
Copy link
Member

neko-kai commented Dec 2, 2023

@OOPMan
This seems to be an inference issue with Scala 3... Using produceRun instead of use avoids it, since most examples use it, I guess we managed not to run into it ourselves yet. https://scastie.scala-lang.org/5HZmALDBQke5uzVXiltr6w

import distage._

trait A:
  def run(s: String) = s + " " + s

class anA extends A

trait B:
  val a: A
  def run(s: String) = s + a.run(s)

class aB(override val a: A) extends B

val testModule = new ModuleDef:
  make[A].from[anA]
  make[B].from[aB]

object Test:
  val injector = Injector()

  def main(args: Array[String]): Unit =
    injector.produceRun(testModule) {
      (b: B) => println(b.run("zing"))
    }

I'll see if I can fix it. In the meantime you should be able to avoid it by passing [Identity] type parameter to methods such as .use explicitly.

@OOPMan
Copy link
Author

OOPMan commented Dec 2, 2023

@neko-kai Oh man, to think it was so simple XD

Thanks a lot :-)

@neko-kai
Copy link
Member

neko-kai commented Dec 2, 2023

@OOPMan
This is actually a pretty weird bug. If I add : Unit to your expression, it now works [1]. Same as if it's : Boolean. It only triggers if a java type such as String or java.lang.Boolean is returned [2]. I'll try to minimize and report it to Dotty team.

@OOPMan
Copy link
Author

OOPMan commented Dec 2, 2023

@neko-kai Oh sweet, I found a weird Scala bug :-)

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

No branches or pull requests

2 participants