I'm using cats-effect
to suspend side effects and came across difficulties when implementing pure functions avoiding error-prone Throwable
s. The problem is that cats.effect.Sync[F[_]]
extends 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
}
}
}
In case e.g. cats.effect.IO
I can convert the effects using NaturalTransform as follows:
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
}
}
)
}
Unfortunately this seems unreliable and error prone-way. In the effectful implementation we are free to throw any kind of throwable which will be reported as UnknownError
.
This does not seem to be more reliable then simply using Throwable
s with try-catch
. Can anyone suggest a better/safer technique for dealing with errors?