softwaremill / macwire

Lightweight and Nonintrusive Scala Dependency Injection Library
https://softwaremill.com/open-source/
Apache License 2.0
1.26k stars 75 forks source link

Extend cats-autowire to handle any effect type #189

Open adamw opened 2 years ago

adamw commented 2 years ago

Currently autowire works only for IO. It would be great if it would:

(a) work for any F[_] (b) verify (if at all possible?) that all passed effects/factories/resources use the same effect type - or rather, that they can be parametrised with the same effect type

mbore commented 2 years ago

The next step may be to verify if it is possible to add support for other return types like ZManaged or an effect that meets some requirements

adamw commented 2 years ago

Supporting ZManaged is a different task - adding a parallel implementation (hopefully sharing some code) for zio. Here it's about supporting code parametrised with the effect type, so if the user provides Resource[F] for some F[_], and where we need to return Resource[F] as well.

adamw commented 2 years ago

See https://github.com/softwaremill/macwire/issues/190 for zio support

mbore commented 2 years ago

ok, makes sense. It would be also good to create a pure autowire implementation. https://github.com/softwaremill/macwire/issues/191

djx314 commented 2 years ago

In scala 2, in fact it's a problem like cps.

https://github.com/ThoughtWorksInc/dsl-domains-cats

In scala 3, it's a problem like cats-effect-cps.

https://github.com/typelevel/cats-effect-cps

I wrote a repo use these to implement wire.

https://github.com/scalax/ce-injection-samples

But it's not what I want. I wrote some suggestion in gitter. I hope it can have some helpful.

djx314 commented 2 years ago

I think just use target effect type to support F[_] will be some difficulties. Wrap a case class like fs2 I think is more "type friendly". Just

// case class[F[_], T](model: F[T])

And provide a simple Monad[IO] with any target effect type, we can simple find the target class which need to inject it's constructor. And as the suggestion in gitter, displays the parameter need in building the constructor injection will broken something about "injection by type". So I think separate effect type and model type can provide a frindly ”type environment“ to solve this problem.

djx314 commented 2 years ago

Just have a test and will bring back some freeback. Sorry for read the code with some misconceptions since I have implemented it in a way with the less correlation.

djx314 commented 1 year ago

@adamw I think injection without F[_] can simply create a model like

// sealed trait Injection
// case class FunctionInjection(val func: Any => Injection, val input: Injection]) extends Injection
// case class ModelInjection(val value: Any) extends Injection

And we can tag this to F[_]. like

// sealed trait Injection
// case class FunctionInjection(val func: Any => Injection, val input: Injection) extends Injection
// case class EffectInjection(val effectValue: F[Injection]) extends Injection
// case class ModelInjection(val value: Any) extends Injection

I plan to use implicit to implement a injection since implicit in scala 2 means context so in scala 3 it calls context abstraction. So the code that in my thinking is something like the injection without cats-effect in macwire.

djx314 commented 1 year ago

In the abstraction of Injection with F[_], we can no use to do something like close, release because Resource and many many things will do this for us. It seems that many things in Spring's Injection now just leave it to F[_]

djx314 commented 1 year ago

Yes, like ZManaged[A & B, Nothing, C], we just print it as

// val a: Injection = implement ZManaged[X, X, A]
// val b: Injection = implement ZManaged[X, X, B]
// val c: Injection = FunctionInjection(func = need => genInjectionC, input = List(a, b))

and we just tag the info in the case class what your ast need. And than we can simply singed it ZManaged[A & B, Nothing, C] just we won't implement this injection dependent on the type in fact.

djx314 commented 1 year ago

tough the email since changing the code above.

adamw commented 1 year ago

Hm, interesting idea - so autowire would generate a description, of how the injection should be done. And then at run-time, we can pick the appropriate effect.

djx314 commented 1 year ago

> Hm, interesting idea - so autowire would generate a description, of how the injection should be done. And then at run-time, we can pick the appropriate effect.

Just a copy of free.

djx314 commented 1 year ago

> Hm, interesting idea - so autowire would generate a description, of how the injection should be done. And then at run-time, we can pick the appropriate effect.

We just add some tags in ast. like

// case class EffectInjection(val effectValue: F[Injection], val tag: InjectionTag) extends Injection

And all effect do the same thing in the ast. Just provide a execute context is ok. And all the effects use different way to target to the trait Injection.

djx314 commented 1 year ago

In EffectInjection, the effect value we can point A in F[A] to an abstraction.

// trait EffectValue[A]
// case class RigthEffectValue[F[_], A](val effectValue: F[A]) extends EffectValue[A]
// case class EitherEffectValue[F[_, _], E, A](vall effectValue: F[E, A]) extends EffectValue[A]
// and so on

certainly, we can add tags to the model. Provide a execute context and it can works well like the trait Injection above. We no need to mind the type it signed, just set all the type to Any in execute context. Then we can

// case class EffectInjection(val effectValue: EffectValue[Injection], val tag: InjectionTag) extends Injection
djx314 commented 1 year ago

Yes, distage just can do each effect target to each ast and use each match case to execute the ast. It can't make all the ast to do the same thing😉.

djx314 commented 1 year ago

Change the code above. From

// case class FunctionInjection(val func: List[Injection] => Injection, val input: List[Injection]) extends Injection

to

// case class FunctionInjection(val func: List[Any] => Injection, val input: List[Injection]) extends Injection

though it's enough

djx314 commented 1 year ago

Change the code above From

// case class FunctionInjection(val func: List[Any] => Injection, val input: List[Injection]) extends Injection

to

// case class FunctionInjection(val func: Any => Injection, val input: Injection) extends Injection

though List[Any] can replace with Currey, Then it's only a free.

djx314 commented 1 year ago

@adamw Wrong, I think all is wrong.

In fact, injection have two parts. 1 is resources, and our app is just dept on part 1 and to be part 2. Part 1 is just 5 - 6 resources, use zio is ok, use cats is ok, use Future or use spirng on java is ok. And get a part 2 from part 1 to run a app is just a shapeless without F[_], or it's macwire's first release.

So if we use two step injection, part 1 can esaily solve by coder himself and part 2 is just macwire's first release.

djx314 commented 1 year ago

I use the macwire(just use wire[Instance]) to implement cats-effect's cats.Resource in this demo1, demo2 with the two step injection described above.

It seems that just the simple macwire is already works well with any effect type, and this demo use some simple way to implement by name injection.

@mbore It seems that this can answer the question in Gitter😊