我正在尝试编写一个函数的版本,用于将实体保存到数据库。我希望这个函数从环境中读取一些SaveOperation[F[_]]
并执行它,并处理可能的失败情况。到目前为止,我想出了两个版本的这个函数:save
是更多态的MTL
版本,而save2
在签名中使用了确切的单子类型,这意味着我只能使用IO
。
type SaveOperation[F[_]] = Employee => F[Int]
def save[F[_] : Monad](employee: Employee)(implicit
A: Ask[F, SaveOperation[F]],
R: Raise[F, AppError]): F[Unit] =
for {
s <- A.ask
rows <- s(employee)
res <- if rows != 1 then R.raise(FailedInsertion)
else ().pure[F]
} yield res
def save2(employee: Employee): Kleisli[IO, SaveOperation[IO], Either[AppError, Unit]] =
Kleisli((saveOperation) => saveOperation(employee)
.handleErrorWith(err => IO.pure(Left(PersistenceError(err))))
.map(rows =>
if rows != 1 then Left(FailedInsertion)
else Right(())
)
)
我稍后可以像这样调用它们:
val repo = new DoobieEmployeeRepository(xa)
val employee = Employee("john", "doe", Set())
type E[A] = Kleisli[IO, SaveOperation[IO], Either[AppError, A]]
println(EmployeeService.save[E](employee).run(repo.save).unsafeRunSync())
println(EmployeeService.save2(employee).run(repo.save).unsafeRunSync())
问题在于调用
save
时我收到了以下错误:Could not find an instance of Monad for E.
I found:
cats.data.Kleisli.catsDataMonadErrorForKleisli[F, A, E]
But method catsDataMonadErrorForKleisli in class KleisliInstances0_5 does not match type cats.Monad[E].
这个错误对我来说似乎没有意义,因为两个函数的签名完全相同,所以单子应该在那里。我怀疑问题出在
Ask [F,SaveOperation [F]]
参数上,这里的F
不是IO
,而SaveOperation
需要IO
。为什么我不能对
save
调用使用Kleisli单子?更新:如果我将类型修改为
E[A] = EitherT [[X] =>> Kleisli [IO,SaveOperation [IO],X],AppError,A]
,则会得到一个新错误:Could not find an implicit instance of Ask[E, SaveOperation[E]]
正确的 SaveOperation 泛型类型应该是 IO,但我无法确定如何通过 Ask 的实例来正确提供它。
type E[A] = EitherT[[X] =>> Kleisli[IO, SaveOperation[IO], X], AppError, A]
,我会得到一个新的错误,我相信这就是我最初试图引用的错误:“找不到Ask [E,SaveOperation [E]]的隐式实例。”我猜SaveOperation
的正确泛型类型应该是IO
,但是正如您所说,这样做将很难组合。 - Leonid Borsave[IO]
时,它实现了我需要的结果。我的原始计划是调用save[Reader[SaveOperation[IO], Either[AppError, *]]]
,但我很难提供这种构造所需的所有隐式参数。您对使用IO
与使用Reader[SaveOperation[IO], Either[AppError, *]]
有什么想法吗?后者似乎更具描述性,因为它指示存在执行副作用并且结果可能失败的依赖项,而纯IO
除了存在副作用外并没有提供太多信息。 - Leonid BorIO[Either[AppError, *]]
。不幸的是,与Zio不同,Cats Effect决定让IO
不带错误通道。但令我困惑的是阅读器——为什么你需要它?是否有Ask
已经足够说明有一个依赖项了呢? - sloucIO
没有显式的错误通道,但这并不意味着没有。每当出现问题时,您可以简单地IO.raiseError(MyErrorWhichExtendsThrowable(...))
,然后在错误处理部分中,您只需模式匹配错误类型以区分例如AppError
和ConnectionError
。但是,如果您希望始终在类型签名中编码您的错误和依赖项,则 zio 绝对是您应该尝试的东西。 - slouc