英文:
EitherT not properly upcasting to common trait super type (Scala 3)
问题
当使用EitherT
链接for推导时,编译器在将错误向上转型为它们的共同祖先类型时出现了问题。
我在我的代码中定义了以下类型:
sealed trait Error
object Error:
case object Error1 extends Error
case class Error2(someParams) extends Error
到目前为止一切都很好。然后我有这个特质,我在其中公开了这些内容:
trait SomeInterface[F[_]]:
def someMethod(params): F[Either[Error1.type, Type1]]
def otherMethod(params): F[Either[Error2, Type2]]
然后在我的逻辑中,我以这种方式链接它们:
for {
t1 <- EitherT(someMethod)
t2 <- EitherT(otherMethod)
} yield t2
理论上这应该可以工作,但我得到了以下错误:
Found: EitherT[F, Error2, Type2]
Expected: EitherT[F, Error1.type, Any]
我已经尝试过更改我的接口,使它们返回共同的类型,但当我实现时它有问题进行转型。
我还尝试在EitherT
构造函数中传递类型参数,但结果相同。
英文:
When chaining a for comprehension with EitherT
, the compiler is having issues upcasting my errors to their common ancestor type.
I have the following type definitions in my code:
sealed trait Error
object Error:
case object Error1 extends Error
case class Error2(someParams) extends Error
So far so good. I then had this trait where I expose this stuff:
trait SomeInterface[F[_]]:
def someMethod(params): F[Either[Error1.type, Type1]]
def otherMethod(params): F[Either[Error2, Type2]]
Then inside my logic I'm chaining them in this way:
for {
t1 <- EitherT(someMethod)
t2 <- EitherT(otherMethod)
yield t2
This should work in theory, but I'm getting the following error:
Found: EitherT[F, Error2, Type2]
Expected: EitherT[F, Error1.type, Any]
I already tried changing my interfaces so that they return the common type, but then it has trouble casting it when I implement.
I also tried passing type parameters in the EitherT
constructor, but same results.
答案1
得分: 2
我不是一个很擅长FP的专家,说实话,我不确定我的解决方案是否是“干净”的,但每当我遇到这种问题时,我都会导入 cats.syntax.bifunctor.*
并在 EitherT
上调用 leftWiden[Error]
。
英文:
Not a great FP expert to be honest so I'm not sure whether my solution is the "clean" one, but whenever I had this kind of problem I fixed by importing cats.syntax.bifunctor.*
and calling leftWiden[Error]
on the EitherT
.
答案2
得分: 2
如@stefanobaghino所说,使用leftWiden[Error]
修复了问题。
错误的根本原因是因为Either
中的Left
类型和cats effect中的EitherT
是不变的,因此会出现类型不匹配的问题。
文章Monad transformers and cats - 3 tips for beginners可能有助于理解原因,并提供有关错误处理的其他提示。
假设您希望将业务错误建模为扩展通用类型
CatError
的类型层次结构:sealed abstract class CatError(msg: String) case object CatIsHungry extends CatError("Cat is hungry!") case object CatIsSleepy extends CatError("Cat is sleepy!") case object CatIsIgnoringYou extends CatError("Cat is ignoring you!") case object CatIsNotInTheMood extends CatError("Cat is not int the mood!")
然后,让我们创建两个方法,都返回嵌套的
IO
和Either
。第一个Either
的错误类型将为CatIsIgnoringYou
,第二个将为CatIsNotInTheMood
。由于两个错误类型都扩展了CatError
,您可能会假设如果您将结果的EitherT
的错误类型修复为CatError
,就不会有编译错误。import cats.data.EitherT import cats.effect.IO case class Cat(readyToPlay: Boolean, hasOtherPlans: Boolean, isSleepy: Boolean) def callTheCat(cat: Cat): IO[Either[CatIsIgnoringYou.type, Cat]] = IO(Either.cond(!cat.hasOtherPlans, cat.copy(readyToPlay = true), CatIsIgnoringYou)) def playWithCat(cat: Cat): IO[Either[CatIsNotInTheMood.type, Unit]] = IO(Either.cond(!cat.isSleepy, (), CatIsNotInTheMood)) //应该可以编译,对吗? def haveFun(cat: Cat): EitherT[IO, CatError, Unit] = for { readyCat <- EitherT(callTheCat(cat)) _ <- EitherT(playWithCat(readyCat)) } yield ()
不幸的是,情况并非如此。
Either
中的Left
类型和cats effect中的EitherT
是不变的,因此会出现类型不匹配的问题:再次修复问题非常简单。您需要使用
leftWiden
函数,它允许您将EitherT
的左类型扩展为更一般的CatError
。通常,只需在flatMap
链或for-comprehension中的最后一个函数调用上使用leftWiden
即可:import cats.data.EitherT import cats.effect.IO import cats.syntax.all._ // 新的导入 // ...相同的代码 def haveFun(cat: Cat): EitherT[IO, CatError, Unit] = for { readyCat <- EitherT(callTheCat(cat)) _ <- EitherT(playWithCat(readyCat)).leftWiden[CatError] // 使用leftWiden来修复问题 } yield ()
英文:
As @stefanobaghino said, using leftWiden[Error]
fix the problem.
The root cause of the error is because Left
type in Either
and EitherT
in cats effect is invariant and you will get type mismatch.
The article Monad transformers and cats - 3 tips for beginners might help to understand why and also show other tips about error handling.
> Let’s say you want to model your business errors as a hierarchy of types extending common type CatError
:
>
> scala
> sealed abstract class CatError(msg: String)
>
> case object CatIsHungry extends CatError("Cat is hungry!")
> case object CatIsSleepy extends CatError("Cat is sleepy!")
> case object CatIsIgnoringYou extends CatError("Cat is ignoring you!")
> case object CatIsNotInTheMood extends CatError("Cat is not int the mood!")
>
>
> Then let’s create two methods both returning nested IO
and Either
. The error type of first Either
will be CatIsIgnoringYou
and CatIsNotInTheMood
for the second one. Since both error types extend CatError
, you > could assume that if you fix the error type of resulting EitherT
to CatError
there would be no compilation errors.
>
> scala
> import cats.data.EitherT
> import cats.effect.IO
>
> case class Cat(readyToPlay: Boolean, hasOtherPlans: Boolean, isSleepy: Boolean)
>
> def callTheCat(cat: Cat): IO[Either[CatIsIgnoringYou.type, Cat]]
> = IO(Either.cond(!cat.hasOtherPlans, cat.copy(readyToPlay = true), CatIsIgnoringYou))
>
> def playWithCat(cat: Cat): IO[Either[CatIsNotInTheMood.type, Unit]] =
> IO(Either.cond(!cat.isSleepy, (), CatIsNotInTheMood))
>
> //shold compile, right?
> def haveFun(cat: Cat): EitherT[IO, CatError, Unit] = for {
> readyCat <- EitherT(callTheCat(cat))
> _ <- EitherT(playWithCat(readyCat))
> } yield ()
>
>
> Unluckily, it’s not the case. Left
type in Either
and EitherT
in cats effect is invariant and you will get type mismatch:
>
>
>
> Again fixing the problem is straightforward. You need to use function leftWiden
which lets you broaden the left type of EitherT
to more general CatError
. It’s usually enough to use leftWiden
on the last function call > in flatMap
chain or for-comprehension:
>
> scala
> import cats.data.EitherT
> import cats.effect.IO
> import cats.syntax.all._ // new import
>
> // ... same code
>
> def haveFun(cat: Cat): EitherT[IO, CatError, Unit] = for {
> readyCat <- EitherT(callTheCat(cat))
> _ <- EitherT(playWithCat(readyCat)).leftWiden[CatError] // used leftWiden to fix the issue
> } yield ()
>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论