EitherT未正确向共同特质的超类型进行向上转型(Scala 3)

huangapple go评论65阅读模式
英文:

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 &lt;- EitherT(someMethod)
  t2 &lt;- 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!")

然后,让我们创建两个方法,都返回嵌套的IOEither。第一个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不变的,因此会出现类型不匹配的问题:

EitherT未正确向共同特质的超类型进行向上转型(Scala 3)

再次修复问题非常简单。您需要使用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
&gt; sealed abstract class CatError(msg: String)
&gt;
&gt; case object CatIsHungry extends CatError(&quot;Cat is hungry!&quot;)
&gt; case object CatIsSleepy extends CatError(&quot;Cat is sleepy!&quot;)
&gt; case object CatIsIgnoringYou extends CatError(&quot;Cat is ignoring you!&quot;)
&gt; case object CatIsNotInTheMood extends CatError(&quot;Cat is not int the mood!&quot;)
&gt;

>
> 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
&gt; import cats.data.EitherT
&gt; import cats.effect.IO
&gt;
&gt; case class Cat(readyToPlay: Boolean, hasOtherPlans: Boolean, isSleepy: Boolean)
&gt;
&gt; def callTheCat(cat: Cat): IO[Either[CatIsIgnoringYou.type, Cat]]
&gt; = IO(Either.cond(!cat.hasOtherPlans, cat.copy(readyToPlay = true), CatIsIgnoringYou))
&gt;
&gt; def playWithCat(cat: Cat): IO[Either[CatIsNotInTheMood.type, Unit]] =
&gt; IO(Either.cond(!cat.isSleepy, (), CatIsNotInTheMood))
&gt;
&gt; //shold compile, right?
&gt; def haveFun(cat: Cat): EitherT[IO, CatError, Unit] = for {
&gt; readyCat &lt;- EitherT(callTheCat(cat))
&gt; _ &lt;- EitherT(playWithCat(readyCat))
&gt; } yield ()
&gt;

>
> Unluckily, it’s not the case. Left type in Either and EitherT in cats effect is invariant and you will get type mismatch:
>
> EitherT未正确向共同特质的超类型进行向上转型(Scala 3)
>
> 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
&gt; import cats.data.EitherT
&gt; import cats.effect.IO
&gt; import cats.syntax.all._ // new import
&gt;
&gt; // ... same code
&gt;
&gt; def haveFun(cat: Cat): EitherT[IO, CatError, Unit] = for {
&gt; readyCat &lt;- EitherT(callTheCat(cat))
&gt; _ &lt;- EitherT(playWithCat(readyCat)).leftWiden[CatError] // used leftWiden to fix the issue
&gt; } yield ()
&gt;

huangapple
  • 本文由 发表于 2023年7月27日 23:16:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76781190.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定