为何基于Cats MTL实现的save方法无法使用Kleisli monad调用
报错原因分析
第一个报错(找不到E的Monad实例)
你最初定义的类型:
type E[A] = Kleisli[IO, SaveOperation[IO], Either[AppError, A]]
本身不满足Monad的结构要求。Kleisli[F, R, A]的Monad实例要求参数F具备Monad实例,且A是任意泛型参数,而你这里直接把Kleisli的返回值固定为Either类型,相当于把错误处理逻辑硬编码在返回值里,没有用Monad Transformer做结构化封装,所以Cats无法推导对应的Monad实例。
第二个报错(找不到Ask[E, SaveOperation[E]]实例)
你调整后的叠层类型本身是合法的Monad:
type E[A] = EitherT[[X] =>> Kleisli[IO, SaveOperation[IO], X], AppError, A]
但问题出在save函数的签名设计:你要求从环境中读取的是SaveOperation[F],也就是和外层业务MonadF绑定的保存操作,而你实际传入环境的repo.save是SaveOperation[IO],两者类型不匹配,自然找不到对应的Ask实例。
而你的save2可以正常运行,是因为它没有用MTL做泛型抽象,硬编码了所有层的类型,直接处理IO的异常和返回值,不存在泛型匹配问题。
解决方案
核心思路是把底层保存操作的IO类型和上层业务Monad解耦,不需要强制让SaveOperation和上层F绑定:
- 调整
save函数的签名,增加LiftIO约束,把IO类型的保存操作提升到上层泛型F中 - 调整Ask依赖的类型为
SaveOperation[IO],和你实际的repo方法类型匹配
修改后的save函数代码如下:
import cats.effect.LiftIO import cats.mtl.Ask import cats.mtl.Raise import cats.syntax.all._ type SaveOperation[F[_]] = Employee => F[Int] def save[F[_] : Monad : LiftIO](employee: Employee)(implicit A: Ask[F, SaveOperation[IO]], R: Raise[F, AppError]): F[Unit] = for { // 从环境读取IO类型的保存操作 saveOp <- A.ask // 把IO操作提升到F,同时把IO异常转换为自定义的AppError rows <- LiftIO[F].liftIO(saveOp(employee)) .adaptError(err => PersistenceError(err)) res <- if rows != 1 then R.raise(FailedInsertion) else ().pure[F] } yield res
调用方式如下:
import cats.data.EitherT import cats.data.Kleisli import cats.mtl.implicits._ val repo = new DoobieEmployeeRepository(xa) val employee = Employee("john", "doe", Set()) // 定义叠层Monad type KleisliIO[A] = Kleisli[IO, SaveOperation[IO], A] type E[A] = EitherT[KleisliIO, AppError, A] // 执行调用 println(EmployeeService.save[E](employee).value.run(repo.save).unsafeRunSync())
内容的提问来源于stack exchange,提问作者Leonid Bor
相关产品推荐
相关产品推荐

