7mind / izumi

Productivity-oriented collection of lightweight fancy stuff for Scala toolchain
https://izumi.7mind.io
BSD 2-Clause "Simplified" License
613 stars 66 forks source link

How to interpret IncompatibleEffectType compilation error #1359

Closed loathwine closed 1 year ago

loathwine commented 3 years ago

Hi, I'm in the process of upgrading from Distage 0.10.3-M2 to 1.0.0. I've managed to run the wiring check during compilation. However, I'm not sure how to interpret the wiring compilation errors.

In my project, we have multiple Kafka consumers. Each consumer is a resource that results in a ConsumerInfo (containing some state info about consumer). This way, to start the consumer we just need to wire it in somewhere.

Basically I have

case class ConsumerInfo(...)

// In ModuleDef (this is the line referred by compilation error)
many[ConsumerInfo].addResource[IntegrationPartnerConsumerResource]
// where IntegrationPartnerConsumerResource extends Lifecycle.Of[ZIO[ZEnv, Throwable, *], ConsumerInfo]
// Note: Used to be ... extends DIResource.FromZIO[ZEnv, Throwable, ConsumerInfo] before 1.0.0

/*
Wiring compilation error (tried to format a bit for readability):
IncompatibleEffectType(
{
set.{type.scala.collection.immutable.Set[=ConsumerInfo]}
/{type.com.company.kafka.ConsumerInfo}
#-1617288746,
{
set.{type.scala.collection.immutable.Set[=ConsumerInfo]}
/{type.com.company.kafka.ConsumerInfo}
#-1617288746 (MyPlugin.scala:135) 

:= 

allocate[λ %0 → zio.ZIO[-{
Has[=package::System::Service] & Has[=package::Clock::Service] & Has[=package::Random::Service] & Has[=package::Blocking::Service] & Has[=package::Console::Service]}
,+Throwable,+0]]{
resource.{
set.{type.scala.collection.immutable.Set[=ConsumerInfo]}
/{type.company.kafka.ConsumerInfo}
#-1617288746/com.company.integration.IntegrationPartnerConsumerResource}
,λ %1 → zio.ZIO[-Any,+Throwable,+1],λ %0 → zio.ZIO[-{Has[=package::System::Service] & Has[=package::Clock::Service] & Has[=package::Random::Service] & Has[=package::Blocking::Service] & Has[=package::Console::Service]}
,+Throwable,+0])
*/

This worked ok before. Could you help me understand what is wrong?

neko-kai commented 3 years ago

@Edvin-san You're probably launching distage with Task as the effect type. ZIO[ZEnv, Throwable, ?] effect is NOT compatible with Task (ZIO[Any, Throwable, ?]) – it is more specific, that is, a Task may be used as if it's a ZIO-ZEnv, because a Task does not use its ZEnv parameter, but the opposite is not true – you can't use a ZIO-ZEnv in place of Task, because it uses its parameter and it will blow up when passed an Any instead of a ZEnv.

It might have worked by accident if you were launching distage from a zio.App entrypoint where ZEnv environment was provided, but it was not typesafe and could be easily broken by using zio.provide(()) to substitute the environment with Unit – which would still be compatible with Any, but would cause a ClassCastException.

Two ways to to do this well here:

  1. You may change the effect type of your app from Task to ZIO[ZEnv, Throwable, ?], then the effect type will be compatible, since Task is compatible ZIO[ZEnv, Throwable, ?] this does not affect other bindings.

  2. You may use .addHas instead of .addResource – distage can provide ZIO environments from DI, (1) – this will transform the binding into a binding for Lifecycle.Of[Task, ?] and the parts of ZEnv will be provided from DI, this will make the effect type of the resource itself compatible with Task without changing the effect type of the applicaiton.

loathwine commented 3 years ago

Thanks! That seems to have been the issue. When you say "the effect type of your app", is that determined by which RoleAppMain is extended? For instance, I have sealed abstract class MainBase(activation: Activation) extends RoleAppMain.LauncherBIO2[zio.IO], which means the effect type is zio.IO, right? I also have a custom Role extending RoleService[Task], but this is not that relevant here if I understand it correctly.

neko-kai commented 3 years ago

@Edvin-san Yes, the effect type is determined by the type parameter of LauncherBIO2 (in distage-testkit by type parameter of Spec1/2/3)

which means the effect type is zio.IO, right?

Not quite, effect type must have a single parameter, so LauncherBIO2 extends from RoleAppMain[F[Throwable, ?]] which makes the effect type zio.IO[Throwable, ?] == zio.Task. To make it zio.ZIO[ZEnv, Throwable, ?], you'll need to pass zio.ZIO[ZEnv, +?, +?] type parameter to LauncherBIO2.

Actually, if you're using RoleAppMain, changing effect type to ZIO-ZEnv may not work very well since some things are hardcoded to Task currently, and you'll need to add bindings for QuasiIO[ZIO[ZEnv, Throwable, ?]] & QuasiIORunner[ZIO[ZEnv, Throwable, ?]] and possibly more into RoleAppMain roleAppBootOverrides pluginConfig to make it launch.

Better check if .addHas[IntegrationPartnerConsumerResource] solves it first, which it's supposed to do well.

loathwine commented 3 years ago

@neko-kai Ah, I see. That explains why I needed the extra binding for my tests since they used the ZIO-ZEnv effect type (mentioned in my other issue).

pshirshov commented 1 year ago

@Edvin-san : can I close this or you see any ways to improve the error messages?..

loathwine commented 1 year ago

@pshirshov I think the error messages might be improved with something like

IncompatibleEffectType (MyPlugin.scala:135)
Expected: ZIO[Any, Throwable, *]
Found: ZIO[Has[System.Service] with Has[Clock.Service] with ..., Throwable, *]

I suspect this would have lead me to the correct conclusion faster. The current error message is a bit overwhelming and some parts are not so obvious in the error, for example allocate[λ %0 → zio.ZIO[-{ ...

However, I also do not mind if you close this issue since I got help with my immediate problem!

pshirshov commented 1 year ago

λ %0 → zio.ZIO

That's how izumi-reflect represent type lambdas. I have zero motivation to support scala style to be honest but would welcome a p/r (with optional rendering in scala style), that's a relatively easy improvement.

Ok, we'll see how can we make this message better.

pshirshov commented 1 year ago

Ok, now it's - {type.com.github.pshirshov.test.plugins.StaticTestRole[=λ %0 → 0]}: injector uses effect λ %0 → 0 but binding uses incompatible effect λ %0 → cats.effect.IO[+0] (StaticTestMain.scala:19)