在scala中以纯粹和安全的方式进行错误转换

kupeojn6  于 2021-07-14  发布在  Java
关注(0)|答案(1)|浏览(353)

我在用 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 . 有人能提出更好/更安全的方法来处理错误吗?

vpfxa7rd

vpfxa7rd1#

当你在黑暗的世界里 IO ,这是无法逃避的事实 Throwable 可能会发生意外。关键是区分真正例外的错误和预期的错误。
试图为野外可能发生的错误建立一个类型化模型是一个永无止境的探索,所以我的建议是不要尝试。相反,决定要具体化到api中的错误,并允许任何其他错误作为错误发生 ThrowablesIO ,这样调用方就可以决定是否以及在何处处理异常情况,同时强制处理预期的错误。
一个非常简单的场景示例可以是:

final case class FileAlreadyExists(path: String)

final class File[F[_]: Sync]{
  def rename(from: String, to: String): F[Either[FileAlreadyExists, Unit]] =
    Sync[F].delay { Files.move(Paths.get(from), Paths.get(to))}.attempt.flatMap {
      case Left(_ : FileAlreadyExistsException) => Sync[F].pure(Left(FileAlreadyExists(to)))
      case Left(e)                              => Sync[F].raiseError(e)
      case Right(_)                             => Sync[F].pure(Right(()))
    }
}

通过这种方式,您可以区分将文件重命名为已存在的文件(在中发生)的预期错误 Either 以及完全意外的错误(这些错误仍然发生在 IO )而且可能在其他地方处理。

相关问题