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

Can't migrate from v0.7: StateT[EitherT, ...], ...] vs. EitherT[StateT, ...], ...] #305

Open
marcinzh opened this issue Jan 25, 2021 · 1 comment

Comments

@marcinzh
Copy link
Contributor

Cats-MTL v0.7.1 allows both arrangements.

Cats-MTL v1.1.1 case works only when EitherT is on the top.

Using: cats-mtl v0.7.1

https://scastie.scala-lang.org/vIEWTZkrTlGyel2jBiwRoA

import cats.{Monad, Id}
import cats.data.{StateT, EitherT}
import cats.implicits._
import cats.mtl.{MonadState, FunctorRaise}
import cats.mtl.implicits._

object Main extends App {
  def computation[F[_]: Monad: MonadState[*[_], Int]: FunctorRaise[*[_], String]](a: Int): F[Int] = a.pure[F]
  type MyEither[A] = EitherT[Id, String, A]
  type MyState[A] = StateT[MyEither, Int, A]
  val result = computation[MyState](42).run(0).value  // <====== NO ERROR
  println(result)
}

Using: cats-mtl v1.1.1

https://scastie.scala-lang.org/bZE05pflSnKOjpPiGmksjg

import cats.{Monad, Id}
import cats.data.{StateT, EitherT}
import cats.implicits._
import cats.mtl.{Stateful, Raise}
import cats.mtl.implicits._

object Main extends App {
  def computation[F[_]: Monad: Stateful[*[_], Int]: Raise[*[_], String]](a: Int): F[Int] = a.pure[F]
  type MyEither[A] = EitherT[Id, String, A]
  type MyState[A] = StateT[MyEither, Int, A]
  val result = computation[MyState](42).run(0).value  //  <======  ERROR
  println(result)
}

Compiler output

Could not find an implicit instance of Raise[Main.MyState, String]. If you have
a good way of handling errors of type String at this location, you may want
to construct a value of type EitherT for this call-site, rather than Main.MyState.
An example type:

  EitherT[Main.MyState, String, *]

This is analogous to writing try/catch around this call. The EitherT will
"catch" the errors of type String.

If you do not wish to handle errors of type String at this location, you should
add an implicit parameter of this type to your function. For example:

  (implicit fraise: Raise[Main.MyState, String])

Manually defining the missing Raise instance using def raise(e) = StateT.liftF(EitherT.leftT(e)) makes it work.

@kczulko
Copy link

kczulko commented Oct 19, 2022

@marcinzh

Just traversing this repo and found your issue here. Intrigued me somehow so I checked it. While looking at your github I am pretty sure you know the fix, however let me post here my findings:

  • If you change Raise to Handle your code will work. Handle extends Raise but it is somehow not being resolved here for the StateT (maybe it's a compiler bug?)
  • so the patch is simple (?) and is about creating a Raise provider for StateT based on Handler.handleStateT function:
diff --git a/core/src/main/scala/cats/mtl/Raise.scala b/core/src/main/scala/cats/mtl/Raise.scala
index a29b70d..ea67cf9 100644
--- a/core/src/main/scala/cats/mtl/Raise.scala
+++ b/core/src/main/scala/cats/mtl/Raise.scala
@@ -124,6 +124,8 @@ private[mtl] trait RaiseInstances extends LowPriorityRaiseInstances {
       F: Monad[F]): Raise[IorT[F, E, *], E] =
     Handle.handleIorT[F, E]

+  implicit final def raiseStateT[F[_], E, S](implicit F0: Handle[F, E], M: Monad[F]): Raise[StateT[F, S, *], E] = Handle.handleStateT
+
 }

It works, however I haven't check if this interferes with other parts of this repository (laws etc.).

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