我在用 cats-effect
在实现纯函数时遇到了一些困难,避免了容易出错 Throwable
s。问题是 cats.effect.Sync[F[_]]
延伸 Bracket[F, Throwable]
.
sealed trait Err
final case class FileExistError(path: String) extends Err
case object UnknownError extends Err
final case class FileExistThrowable(path: String, cause: Throwable) extends Throwable
final class File[F[_]: Sync]{
def rename(from: String, to: String): F[Unit] =
implicitly[Sync[F]] delay {
try{
Files.move(Paths.get(from), Paths.get(to))
} catch {
case e: FileAlreadyExistsException =>
throw FileExistThrowable(to, e)
case e => throw e
}
}
}
万一。 cats.effect.IO
我可以使用naturaltransform转换效果,如下所示:
implicit val naturalTransform: IO ~> EitherT[IO, Err, ?] =
new ~>[IO, EitherT[IO, Err, ?]] {
override def apply[A](fa: IO[A]): EitherT[IO, Err, A] =
EitherT(
fa.attempt map { e =>
e.left map {
case FileExistsThrowable(path, cause) =>
FileExistsError(path)
case NonFatal(e) =>
UnknownError
}
}
)
}
不幸的是,这种方法似乎不可靠且容易出错。在有效的执行中,我们可以随意抛出任何类型的抛出,这些抛出将被报告为 UnknownError
.
这似乎并不比简单地使用 Throwable
s与 try-catch
. 有人能提出更好/更安全的方法来处理错误吗?
1条答案
按热度按时间vpfxa7rd1#
当你在黑暗的世界里
IO
,这是无法逃避的事实Throwable
可能会发生意外。关键是区分真正例外的错误和预期的错误。试图为野外可能发生的错误建立一个类型化模型是一个永无止境的探索,所以我的建议是不要尝试。相反,决定要具体化到api中的错误,并允许任何其他错误作为错误发生
Throwables
在IO
,这样调用方就可以决定是否以及在何处处理异常情况,同时强制处理预期的错误。一个非常简单的场景示例可以是:
通过这种方式,您可以区分将文件重命名为已存在的文件(在中发生)的预期错误
Either
以及完全意外的错误(这些错误仍然发生在IO
)而且可能在其他地方处理。