CanCatch
CanCatch
CanCatch
lets you catch NonFatal
Throwable
in the F[A]
and turned it into F[Either[Throwable, A]]
. It takes a function from Throwable
to your own error type, yet it can handle only NonFatal
ones as already mentioned.
trait CanCatch[F[_]] {
def catchNonFatal[A, B](fb: => F[B])(f: Throwable => A): F[Either[A, B]]
def catchNonFatalEither[A, B](fab: => F[Either[A, B]])(f: Throwable => A): F[Either[A, B]]
def catchNonFatalEitherT[A, B](fab: => EitherT[F, A, B])(f: Throwable => A): EitherT[F, A, B]
}
CanCatch.catchNonFatal
CanCatch[F].catchNonFatal[A, B]
lets you catch NonFatal
Throwable
from F[B]
and returns F[Either[A, B]]
.
How to Use
- IO
- Future
- Id
import cats.effect._
import effectie.cats._
val fa = CanCatch[IO].catchNonFatal(
IO(throw new RuntimeException("Something's wrong!"))
)(identity)
// fa: IO[Either[Throwable, Nothing]] = Map(
// source = Bind(
// source = Delay(thunk = <function0>),
// f = <function1>,
// trace = null
// ),
// f = effectie.CanCatch$$Lambda$12266/0x00000001035e8040@6fa2cba0,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.map(IO.scala:106),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:28),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// repl.MdocSession$App0$.<clinit>(can-catch.md:19),
// repl.MdocSession$App.<init>(can-catch.md:5),
// repl.MdocSession$.app(can-catch.md:3),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$2(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withErr(Console.scala:193),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$1(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withOut(Console.scala:164),
// mdoc.internal.document.DocumentBuilder$$doc$.build(DocumentBuilder.scala:88),
// mdoc.internal.markdown.MarkdownBuilder$.$anonfun$buildDocument$2(MarkdownBuilder.scala:47),
// mdoc.internal.markdown.MarkdownBuilder$$anon$1.run(MarkdownBuilder.scala:104)
// )
// )
// )
fa.unsafeRunSync()
// res1: Either[Throwable, Nothing] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import effectie.cats._
implicit val executorService: ExecutorService = Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
// executorService: ExecutorService = java.util.concurrent.ForkJoinPool@5e275e72[Running, parallelism = 4, size = 2, active = 0, running = 0, steals = 2, tasks = 0, submissions = 0]
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
// ec: ExecutionContext = scala.concurrent.impl.ExecutionContextImpl$$anon$4@dff86c0
val fa = CanCatch[Future].catchNonFatal(
Future(throw new RuntimeException("Something's wrong!"))
)(identity)
// fa: Future[Either[Throwable, Nothing]] = Future(Success(Left(java.lang.RuntimeException: Something's wrong!)))
// Just for this example, you wouldn't want to do it in your production code
Await.result(fa, Duration.Inf)
// res3: Either[Throwable, Nothing] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import effectie.cats._
CanCatch[Id].catchNonFatal(
throw new RuntimeException("Something's wrong!")
)(identity)
// res5: Either[Throwable, Nothing] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
Happy Path Example
- IO
- Future
- Id
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.cats._
import effectie.cats.Effectful._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
}
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatal(
for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b
)(MyError.nonFatalThrowable)
val fa = doSomething[IO](1)
// fa: IO[Either[MyError, Int]] = Map(
// source = Bind(
// source = Bind(
// source = Pure(a = 101),
// f = <function1>,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.flatMap(IO.scala:133),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:886),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:863),
// cats.FlatMap$Ops.flatMap(FlatMap.scala:229),
// cats.FlatMap$Ops.flatMap$(FlatMap.scala:229),
// cats.FlatMap$ToFlatMapOps$$anon$2.flatMap(FlatMap.scala:243),
// repl.MdocSession$App6$$anonfun$doSomething$1.apply(can-catch.md:117),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:31),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// repl.MdocSession$App6$.doSomething(can-catch.md:120),
// repl.MdocSession$App6$.<clinit>(can-catch.md:123),
// repl.MdocSession$App4$.<clinit>(can-catch.md:74),
// repl.MdocSession$App2$.<clinit>(can-catch.md:57),
// repl.MdocSession$App0$.<clinit>(can-catch.md:25),
// repl.MdocSession$App.<init>(can-catch.md:5),
// repl.MdocSession$.app(can-catch.md:3),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$2(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withErr(Console.scala:193),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$1(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withOut(Console.scala:164),
// mdoc.internal.document.DocumentBuilder$$doc$.build(DocumentBuilder.scala:88),
// mdoc.internal.markdown.MarkdownBuilder$.$anonfun$buildDocument$2(MarkdownBuilder.scala:47),
// mdoc.internal.markdown.MarkdownBuilder$$anon$1.run(MarkdownBuilder.scala:104)
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Right(value = 202)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(MyError.NonFatalThrowable(a)) =>
println(s"Result: Failed with $a")
}
// Result is 202
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.concurrent.ExecutorServiceOps
object MyApp {
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
}
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatal(
for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b
)(MyError.nonFatalThrowable)
def main(arg: Array[String]): Unit = {
val executorService: ExecutorService =
Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
try {
val fa = doSomething[Future](1)
println(fa)
val result = Await.result(fa, 1.second)
println(result)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
} finally {
ExecutorServiceOps.shutdownAndAwaitTermination(executorService, 1.second)
}
}
}
MyApp.main(Array.empty)
// Future(<not completed>)
// Right(202)
// Result is 202
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
}
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatal(
for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b
)(MyError.nonFatalThrowable)
val fa = doSomething[Id](1)
// fa: Id[Either[MyError, Int]] = Right(value = 202)
fa match {
case Right(b) =>
println(s"Result is $b")
case Left(MyError.NonFatalThrowable(a)) =>
println(s"Result: Failed with $a")
}
// Result is 202
Unhappy Path Example
- IO
- Future
- Id
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.cats._
import effectie.cats.Effectful._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
}
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatal(
for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b
)(MyError.nonFatalThrowable)
val fa = doSomething[IO](-101)
// fa: IO[Either[MyError, Int]] = Map(
// source = Bind(
// source = Bind(
// source = Pure(a = -1),
// f = <function1>,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.flatMap(IO.scala:133),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:886),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:863),
// cats.FlatMap$Ops.flatMap(FlatMap.scala:229),
// cats.FlatMap$Ops.flatMap$(FlatMap.scala:229),
// cats.FlatMap$ToFlatMapOps$$anon$2.flatMap(FlatMap.scala:243),
// repl.MdocSession$App12$$anonfun$doSomething$7.apply(can-catch.md:317),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:31),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// repl.MdocSession$App12$.doSomething(can-catch.md:320),
// repl.MdocSession$App12$.<clinit>(can-catch.md:323),
// repl.MdocSession$App10$.<clinit>(can-catch.md:274),
// repl.MdocSession$App8$.<clinit>(can-catch.md:217),
// repl.MdocSession$App6$.<clinit>(can-catch.md:137),
// repl.MdocSession$App4$.<clinit>(can-catch.md:74),
// repl.MdocSession$App2$.<clinit>(can-catch.md:57),
// repl.MdocSession$App0$.<clinit>(can-catch.md:25),
// repl.MdocSession$App.<init>(can-catch.md:5),
// repl.MdocSession$.app(can-catch.md:3),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$2(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withErr(Console.scala:193),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$1(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withOut(Console.scala:164),
// mdoc.internal.document.DocumentBuilder$$doc$.build(DocumentBuilder.scala:88),
// mdoc.internal.markdown.MarkdownBuilder$.$anonfun$buildDocument$2(Markd...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative number. [n: -1]
// )
// )
result match {
case Right(b) =>
println(s"Result is $b")
case Left(MyError.NonFatalThrowable(a)) =>
println(s"Result: Failed with $a")
}
// Result: Failed with java.lang.IllegalArgumentException: n cannot be a negative number. [n: -1]
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.concurrent.ExecutorServiceOps
object MyApp {
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
}
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatal(
for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b
)(MyError.nonFatalThrowable)
def main(args: Array[String]): Unit = {
val executorService: ExecutorService =
Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
try {
val fa = doSomething[Future](-101)
println(fa)
val result = Await.result(fa, 1.second)
println(result)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
} finally {
ExecutorServiceOps.shutdownAndAwaitTermination(executorService, 1.second)
}
}
}
MyApp.main(Array.empty)
// Future(<not completed>)
// Left(NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -1]))
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -1])
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
}
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatal(
for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b
)(MyError.nonFatalThrowable)
val fa = doSomething[Id](-101)
// fa: Id[Either[MyError, Int]] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative number. [n: -1]
// )
// )
fa match {
case Right(b) =>
println(s"Result is $b")
case Left(MyError.NonFatalThrowable(a)) =>
println(s"Result: Failed with $a")
}
// Result: Failed with java.lang.IllegalArgumentException: n cannot be a negative number. [n: -1]
CanCatch.catchNonFatalEither
CanCatch[F].catchNonFatalEither[A, B]
lets you catch NonFatal
Throwable
from F[Either[A, B]]
and returns F[Either[A, B]]
.
How to Use
- IO
- Future
- Id
import cats.effect._
import effectie.cats._
val fa = CanCatch[IO].catchNonFatalEither(
IO((throw new RuntimeException("Something's wrong!")): Either[Throwable, Int])
)(identity)
// fa: IO[Either[Throwable, Int]] = Map(
// source = Map(
// source = Bind(
// source = Delay(thunk = <function0>),
// f = <function1>,
// trace = null
// ),
// f = effectie.CanCatch$$Lambda$12266/0x00000001035e8040@721f909a,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.map(IO.scala:106),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:28),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// repl.MdocSession$App0$.<clinit>(can-catch.md:19),
// repl.MdocSession$App.<init>(can-catch.md:5),
// repl.MdocSession$.app(can-catch.md:3),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$2(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withErr(Console.scala:193),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$1(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withOut(Console.scala:164),
// mdoc.internal.document.DocumentBuilder$$doc$.build(DocumentBuilder.scala:88),
// mdoc.internal.markdown.MarkdownBuilder$.$anonfun$buildDocument$2(MarkdownBuilder.scala:47),
// mdoc.internal.markdown.MarkdownBuilder$$anon$1.run(MarkdownBuilder.scala:104)
// )
// )
// ),
// f = effectie.CanCatch$$Lambda$12281/0x000000010361f840@3b386aa0,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.map(IO.scala:106),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:28),
// ...
fa.unsafeRunSync()
// res19: Either[Throwable, Int] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import effectie.cats._
implicit val executorService: ExecutorService = Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
// executorService: ExecutorService = java.util.concurrent.ForkJoinPool@414f6944[Running, parallelism = 4, size = 3, active = 0, running = 0, steals = 3, tasks = 0, submissions = 0]
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
// ec: ExecutionContext = scala.concurrent.impl.ExecutionContextImpl$$anon$4@79a65db3
val fa = CanCatch[Future].catchNonFatalEither(
Future((throw new RuntimeException("Something's wrong!")): Either[Throwable, Int])
)(identity)
// fa: Future[Either[Throwable, Int]] = Future(Success(Left(java.lang.RuntimeException: Something's wrong!)))
// Just for this example, you wouldn't want to do it in your production code
Await.result(fa, Duration.Inf)
// res21: Either[Throwable, Int] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import effectie.cats._
CanCatch[Id].catchNonFatalEither(
(throw new RuntimeException("Something's wrong!")): Either[Throwable, Int]
)(identity)
// res23: Either[Throwable, Int] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
Happy Path Example
- IO
- Future
- Id
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.cats._
import effectie.cats.Effectful._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatalEither(
for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c
)(MyError.nonFatalThrowable)
val fa = doSomething[IO](1)
// fa: IO[Either[MyError, Int]] = Map(
// source = Map(
// source = Bind(
// source = Bind(
// source = Pure(a = Right(value = 100)),
// f = <function1>,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.flatMap(IO.scala:133),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:886),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:863),
// cats.FlatMap$Ops.flatMap(FlatMap.scala:229),
// cats.FlatMap$Ops.flatMap$(FlatMap.scala:229),
// cats.FlatMap$ToFlatMapOps$$anon$2.flatMap(FlatMap.scala:243),
// repl.MdocSession$App24$$anonfun$doSomething$13.apply(can-catch.md:597),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:31),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatalEither(CanCatch.scala:25),
// effectie.CanCatch.catchNonFatalEither$(CanCatch.scala:24),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalEither(CanCatch.scala:26),
// repl.MdocSession$App24$.doSomething(can-catch.md:600),
// repl.MdocSession$App24$.<clinit>(can-catch.md:603),
// repl.MdocSession$App22$.<clinit>(can-catch.md:543),
// repl.MdocSession$App20$.<clinit>(can-catch.md:526),
// repl.MdocSession$App18$.<clinit>(can-catch.md:494),
// repl.MdocSession$App16$.<clinit>(can-catch.md:474),
// repl.MdocSession$App14$.<clinit>(can-catch.md:417),
// repl.MdocSession$App12$.<clinit>(can-catch.md:337),
// repl.MdocSession$App10$.<clinit>(can-catch.md:274),
// repl.MdocSession$App8$.<clinit>(can-catch.md:217),
// repl.MdocSession$App6$.<clinit>(can-catch.md:137),
// repl.MdocSession$App4$.<clinit>(can-catch.md:74),
// repl.MdocSession$App2$.<clinit>(can-catch.md:57),
// repl.MdocSession$App0$.<clinit>(can-catch.md:25),
// repl.MdocSession$App.<init>(can-catch.md:5),
// repl.MdocSession$.app(can-catch.md:3),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$2(DocumentBuilder.scala:89),
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Right(value = 200)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result is 200
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.concurrent.ExecutorServiceOps
object MyApp {
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatalEither(
for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c
)(MyError.nonFatalThrowable)
def main(args: Array[String]): Unit = {
val executorService: ExecutorService =
Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
try {
val fa = doSomething[Future](1)
println(fa)
val result = Await.result(fa, 1.second)
println(result)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
} finally {
ExecutorServiceOps.shutdownAndAwaitTermination(executorService, 1.second)
}
}
}
MyApp.main(Array.empty)
// Future(<not completed>)
// Right(200)
// Result is 200
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatalEither(
for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c
)(MyError.nonFatalThrowable)
val fa = doSomething[Id](1)
// fa: Id[Either[MyError, Int]] = Right(value = 200)
fa match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result is 200
Unhappy Path Example
- IO
- Future
- Id
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.cats._
import effectie.cats.Effectful._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatalEither(
for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c
)(MyError.nonFatalThrowable)
val fa = doSomething[IO](-1)
// fa: IO[Either[MyError, Int]] = Map(
// source = Map(
// source = Bind(
// source = Bind(
// source = Pure(a = Right(value = -100)),
// f = <function1>,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.flatMap(IO.scala:133),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:886),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:863),
// cats.FlatMap$Ops.flatMap(FlatMap.scala:229),
// cats.FlatMap$Ops.flatMap$(FlatMap.scala:229),
// cats.FlatMap$ToFlatMapOps$$anon$2.flatMap(FlatMap.scala:243),
// repl.MdocSession$App30$$anonfun$doSomething$19.apply(can-catch.md:829),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:31),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatalEither(CanCatch.scala:25),
// effectie.CanCatch.catchNonFatalEither$(CanCatch.scala:24),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalEither(CanCatch.scala:26),
// repl.MdocSession$App30$.doSomething(can-catch.md:832),
// repl.MdocSession$App30$.<clinit>(can-catch.md:835),
// repl.MdocSession$App28$.<clinit>(can-catch.md:775),
// repl.MdocSession$App26$.<clinit>(can-catch.md:707),
// repl.MdocSession$App24$.<clinit>(can-catch.md:617),
// repl.MdocSession$App22$.<clinit>(can-catch.md:543),
// repl.MdocSession$App20$.<clinit>(can-catch.md:526),
// repl.MdocSession$App18$.<clinit>(can-catch.md:494),
// repl.MdocSession$App16$.<clinit>(can-catch.md:474),
// repl.MdocSession$App14$.<clinit>(can-catch.md:417),
// repl.MdocSession$App12$.<clinit>(can-catch.md:337),
// repl.MdocSession$App10$.<clinit>(can-catch.md:274),
// repl.MdocSession$App8$.<clinit>(can-catch.md:217),
// repl.MdocSession$App6$.<clinit>(can-catch.md:137),
// repl.MdocSession$App4$.<clinit>(can-catch.md:74),
// repl.MdocSession$App2$.<clinit>(can-catch.md:57),
// repl.MdocSession$App0$.<clinit>(can-catch.md:25),
// repl.MdocSession$App.<init>(can-catch.md:5),
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100]
// )
// )
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100])
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.concurrent.ExecutorServiceOps
object MyApp {
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatalEither(
for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c
)(MyError.nonFatalThrowable)
def main(args: Array[String]): Unit = {
val executorService: ExecutorService =
Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
try {
val fa = doSomething[Future](-1)
println(fa)
val result = Await.result(fa, 1.second)
println(result)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
} finally {
ExecutorServiceOps.shutdownAndAwaitTermination(executorService, 1.second)
}
}
}
MyApp.main(Array.empty)
// Future(<not completed>)
// Left(NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100]))
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100])
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatalEither(
for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c
)(MyError.nonFatalThrowable)
val fa = doSomething[Id](-1)
// fa: Id[Either[MyError, Int]] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100]
// )
// )
fa match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100])
CanCatch.catchNonFatalEitherT
CanCatch[F].catchNonFatalEitherT[A, B]
lets you catch NonFatal
Throwable
from EitherT[F, A, B]
and returns EitherT[F, A, B]
.
How to Use
- IO
- Future
- Id
import cats.data.EitherT
import cats.effect._
import effectie.cats._
val fa = CanCatch[IO].catchNonFatalEitherT(
EitherT(IO((throw new RuntimeException("Something's wrong!")): Either[Throwable, Int]))
)(identity)
// fa: EitherT[IO, Throwable, Int] = EitherT(
// value = Map(
// source = Map(
// source = Bind(
// source = Delay(thunk = <function0>),
// f = <function1>,
// trace = null
// ),
// f = effectie.CanCatch$$Lambda$12266/0x00000001035e8040@77b3995d,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.map(IO.scala:106),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:28),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// repl.MdocSession$App0$.<clinit>(can-catch.md:19),
// repl.MdocSession$App.<init>(can-catch.md:5),
// repl.MdocSession$.app(can-catch.md:3),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$2(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withErr(Console.scala:193),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$1(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withOut(Console.scala:164),
// mdoc.internal.document.DocumentBuilder$$doc$.build(DocumentBuilder.scala:88),
// mdoc.internal.markdown.MarkdownBuilder$.$anonfun$buildDocument$2(MarkdownBuilder.scala:47),
// mdoc.internal.markdown.MarkdownBuilder$$anon$1.run(MarkdownBuilder.scala:104)
// )
// )
// ),
// f = effectie.CanCatch$$Lambda$12281/0x000000010361f840@fa69061,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// ...
fa.value.unsafeRunSync()
// res37: Either[Throwable, Int] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats.data.EitherT
import effectie.cats._
implicit val executorService: ExecutorService = Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
// executorService: ExecutorService = java.util.concurrent.ForkJoinPool@3599dd98[Running, parallelism = 4, size = 4, active = 0, running = 0, steals = 3, tasks = 0, submissions = 0]
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
// ec: ExecutionContext = scala.concurrent.impl.ExecutionContextImpl$$anon$4@7e19555d
val fa = CanCatch[Future].catchNonFatalEitherT(
EitherT(Future((throw new RuntimeException("Something's wrong!")): Either[Throwable, Int]))
)(identity)
// fa: EitherT[Future, Throwable, Int] = EitherT(
// value = Future(Success(Left(java.lang.RuntimeException: Something's wrong!)))
// )
// Just for this example, you wouldn't want to do it in your production code
Await.result(fa.value, Duration.Inf)
// res39: Either[Throwable, Int] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.data.EitherT
import effectie.cats._
val fa = CanCatch[Id].catchNonFatalEitherT(
EitherT((throw new RuntimeException("Something's wrong!")): Id[Either[Throwable, Int]])
)(identity)
// fa: EitherT[Id, Throwable, Int] = EitherT(
// value = Left(value = java.lang.RuntimeException: Something's wrong!)
// )
fa.value
// res41: Id[Either[Throwable, Int]] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
Happy Path Example
- IO
- Future
- Id
import cats._
import cats.syntax.all._
import cats.data.EitherT
import cats.effect._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.EitherTSupport._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatalEitherT(
for {
b <- EitherT(pureOf(divide100By(n)))
c <- eitherTRight[MyError](doSomethingBad(b))
} yield c
)(MyError.nonFatalThrowable).value
val fa = doSomething[IO](1)
// fa: IO[Either[MyError, Int]] = Map(
// source = Map(
// source = Bind(
// source = Bind(
// source = Pure(a = Right(value = 100)),
// f = cats.data.EitherT$$Lambda$12283/0x000000010361e840@158b42b2,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.flatMap(IO.scala:133),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:886),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:863),
// cats.data.EitherT.flatMap(EitherT.scala:403),
// repl.MdocSession$App42$$anonfun$doSomething$25.apply(can-catch.md:1148),
// repl.MdocSession$App42$$anonfun$doSomething$25.apply(can-catch.md:1148),
// effectie.CanCatch.$anonfun$catchNonFatalEitherT$1(CanCatch.scala:28),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:31),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatalEither(CanCatch.scala:25),
// effectie.CanCatch.catchNonFatalEither$(CanCatch.scala:24),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalEither(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatalEitherT(CanCatch.scala:28),
// effectie.CanCatch.catchNonFatalEitherT$(CanCatch.scala:27),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalEitherT(CanCatch.scala:26),
// repl.MdocSession$App42$.doSomething(can-catch.md:1151),
// repl.MdocSession$App42$.<clinit>(can-catch.md:1154),
// repl.MdocSession$App40$.<clinit>(can-catch.md:1088),
// repl.MdocSession$App38$.<clinit>(can-catch.md:1065),
// repl.MdocSession$App36$.<clinit>(can-catch.md:1030),
// repl.MdocSession$App34$.<clinit>(can-catch.md:1007),
// repl.MdocSession$App32$.<clinit>(can-catch.md:939),
// repl.MdocSession$App30$.<clinit>(can-catch.md:849),
// repl.MdocSession$App28$.<clinit>(can-catch.md:775),
// repl.MdocSession$App26$.<clinit>(can-catch.md:707),
// repl.MdocSession$App24$.<clinit>(can-catch.md:617),
// repl.MdocSession$App22$.<clinit>(can-catch.md:543),
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Right(value = 200)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result is 200
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats._
import cats.data.EitherT
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.EitherTSupport._
import effectie.concurrent.ExecutorServiceOps
object MyApp {
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatalEitherT(
for {
b <- EitherT(pureOf(divide100By(n)))
c <- eitherTRight[MyError](doSomethingBad(b))
} yield c
)(MyError.nonFatalThrowable).value
def main(args: Array[String]): Unit = {
val executorService: ExecutorService =
Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
try {
val fa = doSomething[Future](1)
println(fa)
val result = Await.result(fa, 1.second)
println(result)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
} finally {
ExecutorServiceOps.shutdownAndAwaitTermination(executorService, 1.second)
}
}
}
MyApp.main(Array.empty)
// Future(<not completed>)
// Right(200)
// Result is 200
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.data.EitherT
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.EitherTSupport._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatalEitherT(
for {
b <- EitherT(pureOf(divide100By(n)))
c <- eitherTRight[MyError](doSomethingBad(b))
} yield c
)(MyError.nonFatalThrowable).value
val fa = doSomething[Id](1)
// fa: Id[Either[MyError, Int]] = Right(value = 200)
fa match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result is 200
Unhappy Path Example
- IO
- Future
- Id
import cats._
import cats.data.EitherT
import cats.syntax.all._
import cats.effect._
import effectie.cats._
import effectie.cats.EitherTSupport._
import effectie.cats.Effectful._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatalEitherT(
for {
b <- EitherT(pureOf(divide100By(n)))
c <- eitherTRight[MyError](doSomethingBad(b))
} yield c
)(MyError.nonFatalThrowable).value
val fa = doSomething[IO](-1)
// fa: IO[Either[MyError, Int]] = Map(
// source = Map(
// source = Bind(
// source = Bind(
// source = Pure(a = Right(value = -100)),
// f = cats.data.EitherT$$Lambda$12283/0x000000010361e840@2e95fdfa,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.flatMap(IO.scala:133),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:886),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:863),
// cats.data.EitherT.flatMap(EitherT.scala:403),
// repl.MdocSession$App42$$anonfun$doSomething$25.apply(can-catch.md:1148),
// repl.MdocSession$App42$$anonfun$doSomething$25.apply(can-catch.md:1148),
// effectie.CanCatch.$anonfun$catchNonFatalEitherT$1(CanCatch.scala:28),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:31),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatalEither(CanCatch.scala:25),
// effectie.CanCatch.catchNonFatalEither$(CanCatch.scala:24),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalEither(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatalEitherT(CanCatch.scala:28),
// effectie.CanCatch.catchNonFatalEitherT$(CanCatch.scala:27),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalEitherT(CanCatch.scala:26),
// repl.MdocSession$App42$.doSomething(can-catch.md:1151),
// repl.MdocSession$App42$.<clinit>(can-catch.md:1154),
// repl.MdocSession$App40$.<clinit>(can-catch.md:1088),
// repl.MdocSession$App38$.<clinit>(can-catch.md:1065),
// repl.MdocSession$App36$.<clinit>(can-catch.md:1030),
// repl.MdocSession$App34$.<clinit>(can-catch.md:1007),
// repl.MdocSession$App32$.<clinit>(can-catch.md:939),
// repl.MdocSession$App30$.<clinit>(can-catch.md:849),
// repl.MdocSession$App28$.<clinit>(can-catch.md:775),
// repl.MdocSession$App26$.<clinit>(can-catch.md:707),
// repl.MdocSession$App24$.<clinit>(can-catch.md:617),
// repl.MdocSession$App22$.<clinit>(can-catch.md:543),
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100]
// )
// )
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100])
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats._
import cats.data.EitherT
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.EitherTSupport._
import effectie.concurrent.ExecutorServiceOps
object MyApp {
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatalEitherT(
for {
b <- EitherT(pureOf(divide100By(n)))
c <- eitherTRight[MyError](doSomethingBad(b))
} yield c
)(MyError.nonFatalThrowable).value
def main(args: Array[String]): Unit = {
val executorService: ExecutorService =
Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
try {
val fa = doSomething[Future](-1)
println(fa)
val result = Await.result(fa, 1.second)
println(result)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
} finally {
ExecutorServiceOps.shutdownAndAwaitTermination(executorService, 1.second)
}
}
}
MyApp.main(Array.empty)
// Future(<not completed>)
// Left(NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100]))
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100])
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.data.EitherT
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.EitherTSupport._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
CanCatch[F].catchNonFatalEitherT(
for {
b <- EitherT(pureOf(divide100By(n)))
c <- eitherTRight[MyError](doSomethingBad(b))
} yield c
)(MyError.nonFatalThrowable).value
val fa = doSomething[Id](-1)
// fa: Id[Either[MyError, Int]] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100]
// )
// )
fa match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100])
Catching
Catching.catchNonFatal
provides a convenient way to use CanCatch
to catch NonFatal
Throwable
in the F[A]
and turned it into F[Either[Throwable, A]]
. Just like CanCatch
, it takes a function from Throwable
to your own error type, yet it can handle only NonFatal
ones as already mentioned.
Catching.catchNonFatal
catchNonFatal
lets you catch NonFatal
Throwable
from F[B]
and returns F[Either[A, B]]
.
How to Use
- IO
- Future
- Id
import cats.effect._
import effectie.cats.Catching._
val fa = catchNonFatal(
IO(throw new RuntimeException("Something's wrong!"))
)(identity)
// fa: IO[Either[Throwable, Nothing]] = Map(
// source = Bind(
// source = Delay(thunk = <function0>),
// f = <function1>,
// trace = null
// ),
// f = effectie.CanCatch$$Lambda$12266/0x00000001035e8040@551b90ad,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.map(IO.scala:106),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:28),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// repl.MdocSession$App0$.<clinit>(can-catch.md:19),
// repl.MdocSession$App.<init>(can-catch.md:5),
// repl.MdocSession$.app(can-catch.md:3),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$2(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withErr(Console.scala:193),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$1(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withOut(Console.scala:164),
// mdoc.internal.document.DocumentBuilder$$doc$.build(DocumentBuilder.scala:88),
// mdoc.internal.markdown.MarkdownBuilder$.$anonfun$buildDocument$2(MarkdownBuilder.scala:47),
// mdoc.internal.markdown.MarkdownBuilder$$anon$1.run(MarkdownBuilder.scala:104)
// )
// )
// )
fa.unsafeRunSync()
// res55: Either[Throwable, Nothing] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import effectie.cats.Catching._
implicit val executorService: ExecutorService = Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
// executorService: ExecutorService = java.util.concurrent.ForkJoinPool@619c84ee[Running, parallelism = 4, size = 3, active = 0, running = 0, steals = 2, tasks = 0, submissions = 0]
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
// ec: ExecutionContext = scala.concurrent.impl.ExecutionContextImpl$$anon$4@2e4dd4bd
val fa = catchNonFatal(
Future(throw new RuntimeException("Something's wrong!"))
)(identity)
// fa: Future[Either[Throwable, Nothing]] = Future(Success(Left(java.lang.RuntimeException: Something's wrong!)))
// Just for this example, you wouldn't want to do it in your production code
Await.result(fa, Duration.Inf)
// res57: Either[Throwable, Nothing] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import effectie.cats.Catching._
catchNonFatal[Id](
throw new RuntimeException("Something's wrong!")
)(identity)
// res59: Id[Either[Throwable, Nothing]] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
Happy Path Example
- IO
- Future
- Id
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
}
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatal(
for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b
)(MyError.nonFatalThrowable)
val fa = doSomething[IO](1)
// fa: IO[Either[MyError, Int]] = Map(
// source = Bind(
// source = Bind(
// source = Pure(a = 101),
// f = <function1>,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.flatMap(IO.scala:133),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:886),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:863),
// cats.FlatMap$Ops.flatMap(FlatMap.scala:229),
// cats.FlatMap$Ops.flatMap$(FlatMap.scala:229),
// cats.FlatMap$ToFlatMapOps$$anon$2.flatMap(FlatMap.scala:243),
// repl.MdocSession$App60$$anonfun$doSomething$37.apply(can-catch.md:1703),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:31),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// effectie.cats.Catching$CurriedCanCatch2$.apply$extension(Catching.scala:45),
// repl.MdocSession$App60$.doSomething(can-catch.md:1706),
// repl.MdocSession$App60$.<clinit>(can-catch.md:1709),
// repl.MdocSession$App58$.<clinit>(can-catch.md:1657),
// repl.MdocSession$App56$.<clinit>(can-catch.md:1640),
// repl.MdocSession$App54$.<clinit>(can-catch.md:1608),
// repl.MdocSession$App52$.<clinit>(can-catch.md:1588),
// repl.MdocSession$App50$.<clinit>(can-catch.md:1514),
// repl.MdocSession$App48$.<clinit>(can-catch.md:1418),
// repl.MdocSession$App46$.<clinit>(can-catch.md:1338),
// repl.MdocSession$App44$.<clinit>(can-catch.md:1264),
// repl.MdocSession$App42$.<clinit>(can-catch.md:1168),
// repl.MdocSession$App40$.<clinit>(can-catch.md:1088),
// repl.MdocSession$App38$.<clinit>(can-catch.md:1065),
// repl.MdocSession$App36$.<clinit>(can-catch.md:1030),
// repl.MdocSession$App34$.<clinit>(can-catch.md:1007),
// repl.MdocSession$App32$.<clinit>(can-catch.md:939),
// repl.MdocSession$App30$.<clinit>(can-catch.md:849),
// repl.MdocSession$App28$.<clinit>(can-catch.md:775),
// repl.MdocSession$App26$.<clinit>(can-catch.md:707),
// repl.MdocSession$App24$.<clinit>(can-catch.md:617),
// repl.MdocSession$App22$.<clinit>(can-catch.md:543),
// repl.MdocSession$App20$.<clinit>(can-catch.md:526),
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Right(value = 202)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(MyError.NonFatalThrowable(a)) =>
println(s"Result: Failed with $a")
}
// Result is 202
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
import effectie.concurrent.ExecutorServiceOps
object MyApp {
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
}
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatal(
for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b
)(MyError.nonFatalThrowable)
def main(arg: Array[String]): Unit = {
val executorService: ExecutorService =
Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
try {
val fa = doSomething[Future](1)
println(fa)
val result = Await.result(fa, 1.second)
println(result)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
} finally {
ExecutorServiceOps.shutdownAndAwaitTermination(executorService, 1.second)
}
}
}
MyApp.main(Array.empty)
// Future(<not completed>)
// Right(202)
// Result is 202
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
}
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatal(
for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b
)(MyError.nonFatalThrowable)
val fa = doSomething[Id](1)
// fa: Id[Either[MyError, Int]] = Right(value = 202)
fa match {
case Right(b) =>
println(s"Result is $b")
case Left(MyError.NonFatalThrowable(a)) =>
println(s"Result: Failed with $a")
}
// Result is 202
Unhappy Path Example
- IO
- Future
- Id
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
}
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatal(
for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b
)(MyError.nonFatalThrowable)
val fa = doSomething[IO](-101)
// fa: IO[Either[MyError, Int]] = Map(
// source = Bind(
// source = Bind(
// source = Pure(a = -1),
// f = <function1>,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.flatMap(IO.scala:133),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:886),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:863),
// cats.FlatMap$Ops.flatMap(FlatMap.scala:229),
// cats.FlatMap$Ops.flatMap$(FlatMap.scala:229),
// cats.FlatMap$ToFlatMapOps$$anon$2.flatMap(FlatMap.scala:243),
// repl.MdocSession$App66$$anonfun$doSomething$43.apply(can-catch.md:1913),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:31),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// effectie.cats.Catching$CurriedCanCatch2$.apply$extension(Catching.scala:45),
// repl.MdocSession$App66$.doSomething(can-catch.md:1916),
// repl.MdocSession$App66$.<clinit>(can-catch.md:1919),
// repl.MdocSession$App64$.<clinit>(can-catch.md:1867),
// repl.MdocSession$App62$.<clinit>(can-catch.md:1807),
// repl.MdocSession$App60$.<clinit>(can-catch.md:1723),
// repl.MdocSession$App58$.<clinit>(can-catch.md:1657),
// repl.MdocSession$App56$.<clinit>(can-catch.md:1640),
// repl.MdocSession$App54$.<clinit>(can-catch.md:1608),
// repl.MdocSession$App52$.<clinit>(can-catch.md:1588),
// repl.MdocSession$App50$.<clinit>(can-catch.md:1514),
// repl.MdocSession$App48$.<clinit>(can-catch.md:1418),
// repl.MdocSession$App46$.<clinit>(can-catch.md:1338),
// repl.MdocSession$App44$.<clinit>(can-catch.md:1264),
// repl.MdocSession$App42$.<clinit>(can-catch.md:1168),
// repl.MdocSession$App40$.<clinit>(can-catch.md:1088),
// repl.MdocSession$App38$.<clinit>(can-catch.md:1065),
// repl.MdocSession$App36$.<clinit>(can-catch.md:1030),
// repl.MdocSession$App34$.<clinit>(can-catch.md:1007),
// repl.MdocSession$App32$.<clinit>(can-catch.md:939),
// repl.MdocSession$App30$.<clinit>(can-catch.md:849),
// repl.MdocSession$App28$.<clinit>(can-catch.md:775),
// repl.MdocSession$App26$.<clinit>(can-catch.md:707),
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative number. [n: -1]
// )
// )
result match {
case Right(b) =>
println(s"Result is $b")
case Left(MyError.NonFatalThrowable(a)) =>
println(s"Result: Failed with $a")
}
// Result: Failed with java.lang.IllegalArgumentException: n cannot be a negative number. [n: -1]
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
import effectie.concurrent.ExecutorServiceOps
object MyApp {
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
}
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatal(
for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b
)(MyError.nonFatalThrowable)
def main(args: Array[String]): Unit = {
val executorService: ExecutorService =
Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
try {
val fa = doSomething[Future](-101)
println(fa)
val result = Await.result(fa, 1.second)
println(result)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
} finally {
ExecutorServiceOps.shutdownAndAwaitTermination(executorService, 1.second)
}
}
}
MyApp.main(Array.empty)
// Future(<not completed>)
// Left(NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -1]))
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -1])
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
}
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatal(
for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b
)(MyError.nonFatalThrowable)
val fa = doSomething[Id](-101)
// fa: Id[Either[MyError, Int]] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative number. [n: -1]
// )
// )
fa match {
case Right(b) =>
println(s"Result is $b")
case Left(MyError.NonFatalThrowable(a)) =>
println(s"Result: Failed with $a")
}
// Result: Failed with java.lang.IllegalArgumentException: n cannot be a negative number. [n: -1]
Catching.catchNonFatalEither
Catching.catchNonFatalEither
provides a convenient way to use CanCatch
to catch NonFatal
Throwable
from F[Either[A, B]]
and returns F[Either[A, B]]
.
How to Use
- IO
- Future
- Id
import cats.effect._
import effectie.cats.Catching._
val fa = catchNonFatalEither(
IO((throw new RuntimeException("Something's wrong!")): Either[Throwable, Int])
)(identity)
// fa: IO[Either[Throwable, Int]] = Map(
// source = Map(
// source = Bind(
// source = Delay(thunk = <function0>),
// f = <function1>,
// trace = null
// ),
// f = effectie.CanCatch$$Lambda$12266/0x00000001035e8040@2fb696a3,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.map(IO.scala:106),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:28),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// repl.MdocSession$App0$.<clinit>(can-catch.md:19),
// repl.MdocSession$App.<init>(can-catch.md:5),
// repl.MdocSession$.app(can-catch.md:3),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$2(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withErr(Console.scala:193),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$1(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withOut(Console.scala:164),
// mdoc.internal.document.DocumentBuilder$$doc$.build(DocumentBuilder.scala:88),
// mdoc.internal.markdown.MarkdownBuilder$.$anonfun$buildDocument$2(MarkdownBuilder.scala:47),
// mdoc.internal.markdown.MarkdownBuilder$$anon$1.run(MarkdownBuilder.scala:104)
// )
// )
// ),
// f = effectie.CanCatch$$Lambda$12281/0x000000010361f840@2621f500,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.map(IO.scala:106),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:28),
// ...
fa.unsafeRunSync()
// res73: Either[Throwable, Int] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import effectie.cats.Catching._
implicit val executorService: ExecutorService = Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
// executorService: ExecutorService = java.util.concurrent.ForkJoinPool@239fbede[Running, parallelism = 4, size = 4, active = 0, running = 0, steals = 3, tasks = 0, submissions = 0]
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
// ec: ExecutionContext = scala.concurrent.impl.ExecutionContextImpl$$anon$4@63b76e2c
val fa = catchNonFatalEither(
Future((throw new RuntimeException("Something's wrong!")): Either[Throwable, Int])
)(identity)
// fa: Future[Either[Throwable, Int]] = Future(Success(Left(java.lang.RuntimeException: Something's wrong!)))
// Just for this example, you wouldn't want to do it in your production code
Await.result(fa, Duration.Inf)
// res75: Either[Throwable, Int] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import effectie.cats.Catching._
catchNonFatalEither[Id](
(throw new RuntimeException("Something's wrong!")): Either[Throwable, Int]
)(identity)
// res77: Id[Either[Throwable, Int]] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
Happy Path Example
- IO
- Future
- Id
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatalEither(
for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c
)(MyError.nonFatalThrowable)
val fa = doSomething[IO](1)
// fa: IO[Either[MyError, Int]] = Map(
// source = Map(
// source = Bind(
// source = Bind(
// source = Pure(a = Right(value = 100)),
// f = <function1>,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.flatMap(IO.scala:133),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:886),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:863),
// cats.FlatMap$Ops.flatMap(FlatMap.scala:229),
// cats.FlatMap$Ops.flatMap$(FlatMap.scala:229),
// cats.FlatMap$ToFlatMapOps$$anon$2.flatMap(FlatMap.scala:243),
// repl.MdocSession$App78$$anonfun$doSomething$49.apply(can-catch.md:2202),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:31),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatalEither(CanCatch.scala:25),
// effectie.CanCatch.catchNonFatalEither$(CanCatch.scala:24),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalEither(CanCatch.scala:26),
// effectie.cats.Catching$CurriedCanCatchEither2$.apply$extension(Catching.scala:74),
// repl.MdocSession$App78$.doSomething(can-catch.md:2205),
// repl.MdocSession$App78$.<clinit>(can-catch.md:2208),
// repl.MdocSession$App76$.<clinit>(can-catch.md:2145),
// repl.MdocSession$App74$.<clinit>(can-catch.md:2128),
// repl.MdocSession$App72$.<clinit>(can-catch.md:2096),
// repl.MdocSession$App70$.<clinit>(can-catch.md:2076),
// repl.MdocSession$App68$.<clinit>(can-catch.md:2016),
// repl.MdocSession$App66$.<clinit>(can-catch.md:1933),
// repl.MdocSession$App64$.<clinit>(can-catch.md:1867),
// repl.MdocSession$App62$.<clinit>(can-catch.md:1807),
// repl.MdocSession$App60$.<clinit>(can-catch.md:1723),
// repl.MdocSession$App58$.<clinit>(can-catch.md:1657),
// repl.MdocSession$App56$.<clinit>(can-catch.md:1640),
// repl.MdocSession$App54$.<clinit>(can-catch.md:1608),
// repl.MdocSession$App52$.<clinit>(can-catch.md:1588),
// repl.MdocSession$App50$.<clinit>(can-catch.md:1514),
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Right(value = 200)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result is 200
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
import effectie.concurrent.ExecutorServiceOps
object MyApp {
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatalEither(
for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c
)(MyError.nonFatalThrowable)
def main(args: Array[String]): Unit = {
val executorService: ExecutorService =
Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
try {
val fa = doSomething[Future](1)
println(fa)
val result = Await.result(fa, 1.second)
println(result)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
} finally {
ExecutorServiceOps.shutdownAndAwaitTermination(executorService, 1.second)
}
}
}
MyApp.main(Array.empty)
// Future(<not completed>)
// Right(200)
// Result is 200
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatalEither(
for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c
)(MyError.nonFatalThrowable)
val fa = doSomething[Id](1)
// fa: Id[Either[MyError, Int]] = Right(value = 200)
fa match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result is 200
Unhappy Path Example
- IO
- Future
- Id
import cats._
import cats.syntax.all._
import cats.effect._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatalEither(
for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c
)(MyError.nonFatalThrowable)
val fa = doSomething[IO](-1)
// fa: IO[Either[MyError, Int]] = Map(
// source = Map(
// source = Bind(
// source = Bind(
// source = Pure(a = Right(value = -100)),
// f = <function1>,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.flatMap(IO.scala:133),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:886),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:863),
// cats.FlatMap$Ops.flatMap(FlatMap.scala:229),
// cats.FlatMap$Ops.flatMap$(FlatMap.scala:229),
// cats.FlatMap$ToFlatMapOps$$anon$2.flatMap(FlatMap.scala:243),
// repl.MdocSession$App84$$anonfun$doSomething$55.apply(can-catch.md:2443),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:31),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatalEither(CanCatch.scala:25),
// effectie.CanCatch.catchNonFatalEither$(CanCatch.scala:24),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalEither(CanCatch.scala:26),
// effectie.cats.Catching$CurriedCanCatchEither2$.apply$extension(Catching.scala:74),
// repl.MdocSession$App84$.doSomething(can-catch.md:2446),
// repl.MdocSession$App84$.<clinit>(can-catch.md:2449),
// repl.MdocSession$App82$.<clinit>(can-catch.md:2386),
// repl.MdocSession$App80$.<clinit>(can-catch.md:2315),
// repl.MdocSession$App78$.<clinit>(can-catch.md:2222),
// repl.MdocSession$App76$.<clinit>(can-catch.md:2145),
// repl.MdocSession$App74$.<clinit>(can-catch.md:2128),
// repl.MdocSession$App72$.<clinit>(can-catch.md:2096),
// repl.MdocSession$App70$.<clinit>(can-catch.md:2076),
// repl.MdocSession$App68$.<clinit>(can-catch.md:2016),
// repl.MdocSession$App66$.<clinit>(can-catch.md:1933),
// repl.MdocSession$App64$.<clinit>(can-catch.md:1867),
// repl.MdocSession$App62$.<clinit>(can-catch.md:1807),
// repl.MdocSession$App60$.<clinit>(can-catch.md:1723),
// repl.MdocSession$App58$.<clinit>(can-catch.md:1657),
// repl.MdocSession$App56$.<clinit>(can-catch.md:1640),
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100]
// )
// )
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100])
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
import effectie.concurrent.ExecutorServiceOps
object MyApp {
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatalEither(
for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c
)(MyError.nonFatalThrowable)
def main(args: Array[String]): Unit = {
val executorService: ExecutorService =
Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
try {
val fa = doSomething[Future](-1)
println(fa)
val result = Await.result(fa, 1.second)
println(result)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
} finally {
ExecutorServiceOps.shutdownAndAwaitTermination(executorService, 1.second)
}
}
}
MyApp.main(Array.empty)
// Future(<not completed>)
// Left(NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100]))
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100])
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatalEither(
for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c
)(MyError.nonFatalThrowable)
val fa = doSomething[Id](-1)
// fa: Id[Either[MyError, Int]] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100]
// )
// )
fa match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100])
Catching.catchNonFatalEitherT
Catching.catchNonFatalEitherT
provides a convenient way to use CanCatch
to catch NonFatal
Throwable
from EitherT[F, A, B]
and returns EitherT[F, A, B]
.
How to Use
- IO
- Future
- Id
import cats.data.EitherT
import cats.effect._
import effectie.cats.Catching._
val fa = catchNonFatalEitherT[IO](
EitherT(IO((throw new RuntimeException("Something's wrong!")): Either[Throwable, Int]))
)(identity)
// fa: EitherT[IO, Throwable, Int] = EitherT(
// value = Map(
// source = Map(
// source = Bind(
// source = Delay(thunk = <function0>),
// f = <function1>,
// trace = null
// ),
// f = effectie.CanCatch$$Lambda$12266/0x00000001035e8040@782fff77,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.map(IO.scala:106),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:28),
// effectie.cats.CanCatch$CanCatchIo$.mapFa(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// repl.MdocSession$App0$.<clinit>(can-catch.md:19),
// repl.MdocSession$App.<init>(can-catch.md:5),
// repl.MdocSession$.app(can-catch.md:3),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$2(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withErr(Console.scala:193),
// mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$1(DocumentBuilder.scala:89),
// scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
// scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
// scala.Console$.withOut(Console.scala:164),
// mdoc.internal.document.DocumentBuilder$$doc$.build(DocumentBuilder.scala:88),
// mdoc.internal.markdown.MarkdownBuilder$.$anonfun$buildDocument$2(MarkdownBuilder.scala:47),
// mdoc.internal.markdown.MarkdownBuilder$$anon$1.run(MarkdownBuilder.scala:104)
// )
// )
// ),
// f = effectie.CanCatch$$Lambda$12281/0x000000010361f840@518f8c87,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// ...
fa.value.unsafeRunSync()
// res91: Either[Throwable, Int] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats.data.EitherT
import effectie.cats.Catching._
implicit val executorService: ExecutorService = Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
// executorService: ExecutorService = java.util.concurrent.ForkJoinPool@f38a126[Running, parallelism = 4, size = 4, active = 0, running = 0, steals = 3, tasks = 0, submissions = 0]
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
// ec: ExecutionContext = scala.concurrent.impl.ExecutionContextImpl$$anon$4@57a678e3
val fa = catchNonFatalEitherT[Future](
EitherT(Future((throw new RuntimeException("Something's wrong!")): Either[Throwable, Int]))
)(identity)
// fa: EitherT[Future, Throwable, Int] = EitherT(
// value = Future(Success(Left(java.lang.RuntimeException: Something's wrong!)))
// )
// Just for this example, you wouldn't want to do it in your production code
Await.result(fa.value, Duration.Inf)
// res93: Either[Throwable, Int] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.data.EitherT
import effectie.cats.Catching._
val fa = catchNonFatalEitherT[Id](
EitherT((throw new RuntimeException("Something's wrong!")): Id[Either[Throwable, Int]])
)(identity)
// fa: EitherT[Id, Throwable, Int] = EitherT(
// value = Left(value = java.lang.RuntimeException: Something's wrong!)
// )
fa.value
// res95: Id[Either[Throwable, Int]] = Left(
// value = java.lang.RuntimeException: Something's wrong!
// )
Happy Path Example
- IO
- Future
- Id
import cats._
import cats.syntax.all._
import cats.data.EitherT
import cats.effect._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
import effectie.cats.EitherTSupport._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatalEitherT(
for {
b <- EitherT(pureOf(divide100By(n)))
c <- eitherTRight[MyError](doSomethingBad(b))
} yield c
)(MyError.nonFatalThrowable).value
val fa = doSomething[IO](1)
// fa: IO[Either[MyError, Int]] = Map(
// source = Map(
// source = Bind(
// source = Bind(
// source = Pure(a = Right(value = 100)),
// f = cats.data.EitherT$$Lambda$12283/0x000000010361e840@5075f127,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.flatMap(IO.scala:133),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:886),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:863),
// cats.data.EitherT.flatMap(EitherT.scala:403),
// repl.MdocSession$App42$$anonfun$doSomething$25.apply(can-catch.md:1148),
// repl.MdocSession$App42$$anonfun$doSomething$25.apply(can-catch.md:1148),
// effectie.CanCatch.$anonfun$catchNonFatalEitherT$1(CanCatch.scala:28),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:31),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatalEither(CanCatch.scala:25),
// effectie.CanCatch.catchNonFatalEither$(CanCatch.scala:24),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalEither(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatalEitherT(CanCatch.scala:28),
// effectie.CanCatch.catchNonFatalEitherT$(CanCatch.scala:27),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalEitherT(CanCatch.scala:26),
// repl.MdocSession$App42$.doSomething(can-catch.md:1151),
// repl.MdocSession$App42$.<clinit>(can-catch.md:1154),
// repl.MdocSession$App40$.<clinit>(can-catch.md:1088),
// repl.MdocSession$App38$.<clinit>(can-catch.md:1065),
// repl.MdocSession$App36$.<clinit>(can-catch.md:1030),
// repl.MdocSession$App34$.<clinit>(can-catch.md:1007),
// repl.MdocSession$App32$.<clinit>(can-catch.md:939),
// repl.MdocSession$App30$.<clinit>(can-catch.md:849),
// repl.MdocSession$App28$.<clinit>(can-catch.md:775),
// repl.MdocSession$App26$.<clinit>(can-catch.md:707),
// repl.MdocSession$App24$.<clinit>(can-catch.md:617),
// repl.MdocSession$App22$.<clinit>(can-catch.md:543),
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Right(value = 200)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result is 200
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats._
import cats.data.EitherT
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
import effectie.cats.EitherTSupport._
import effectie.concurrent.ExecutorServiceOps
object MyApp {
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatalEitherT(
for {
b <- EitherT(pureOf(divide100By(n)))
c <- eitherTRight[MyError](doSomethingBad(b))
} yield c
)(MyError.nonFatalThrowable).value
def main(args: Array[String]): Unit = {
val executorService: ExecutorService =
Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
try {
val fa = doSomething[Future](1)
println(fa)
val result = Await.result(fa, 1.second)
println(result)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
} finally {
ExecutorServiceOps.shutdownAndAwaitTermination(executorService, 1.second)
}
}
}
MyApp.main(Array.empty)
// Future(<not completed>)
// Right(200)
// Result is 200
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.data.EitherT
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
import effectie.cats.EitherTSupport._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatalEitherT(
for {
b <- EitherT(pureOf(divide100By(n)))
c <- eitherTRight[MyError](doSomethingBad(b))
} yield c
)(MyError.nonFatalThrowable).value
val fa = doSomething[Id](1)
// fa: Id[Either[MyError, Int]] = Right(value = 200)
fa match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result is 200
Unhappy Path Example
- IO
- Future
- Id
import cats._
import cats.data.EitherT
import cats.syntax.all._
import cats.effect._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
import effectie.cats.EitherTSupport._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatalEitherT(
for {
b <- EitherT(pureOf(divide100By(n)))
c <- eitherTRight[MyError](doSomethingBad(b))
} yield c
)(MyError.nonFatalThrowable).value
val fa = doSomething[IO](-1)
// fa: IO[Either[MyError, Int]] = Map(
// source = Map(
// source = Bind(
// source = Bind(
// source = Pure(a = Right(value = -100)),
// f = cats.data.EitherT$$Lambda$12283/0x000000010361e840@7db3b41a,
// trace = StackTrace(
// stackTrace = List(
// cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
// cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
// cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
// cats.effect.IO.flatMap(IO.scala:133),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:886),
// cats.effect.IOLowPriorityInstances$IOEffect.flatMap(IO.scala:863),
// cats.data.EitherT.flatMap(EitherT.scala:403),
// repl.MdocSession$App42$$anonfun$doSomething$25.apply(can-catch.md:1148),
// repl.MdocSession$App42$$anonfun$doSomething$25.apply(can-catch.md:1148),
// effectie.CanCatch.$anonfun$catchNonFatalEitherT$1(CanCatch.scala:28),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:31),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalThrowable(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatal(CanCatch.scala:22),
// effectie.CanCatch.catchNonFatal$(CanCatch.scala:21),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatal(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatalEither(CanCatch.scala:25),
// effectie.CanCatch.catchNonFatalEither$(CanCatch.scala:24),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalEither(CanCatch.scala:26),
// effectie.CanCatch.catchNonFatalEitherT(CanCatch.scala:28),
// effectie.CanCatch.catchNonFatalEitherT$(CanCatch.scala:27),
// effectie.cats.CanCatch$CanCatchIo$.catchNonFatalEitherT(CanCatch.scala:26),
// repl.MdocSession$App42$.doSomething(can-catch.md:1151),
// repl.MdocSession$App42$.<clinit>(can-catch.md:1154),
// repl.MdocSession$App40$.<clinit>(can-catch.md:1088),
// repl.MdocSession$App38$.<clinit>(can-catch.md:1065),
// repl.MdocSession$App36$.<clinit>(can-catch.md:1030),
// repl.MdocSession$App34$.<clinit>(can-catch.md:1007),
// repl.MdocSession$App32$.<clinit>(can-catch.md:939),
// repl.MdocSession$App30$.<clinit>(can-catch.md:849),
// repl.MdocSession$App28$.<clinit>(can-catch.md:775),
// repl.MdocSession$App26$.<clinit>(can-catch.md:707),
// repl.MdocSession$App24$.<clinit>(can-catch.md:617),
// repl.MdocSession$App22$.<clinit>(can-catch.md:543),
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100]
// )
// )
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100])
import java.util.concurrent.{ExecutorService, Executors}
import scala.concurrent.{ExecutionContext, Future, Await}
import scala.concurrent.duration._
import cats._
import cats.data.EitherT
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
import effectie.cats.EitherTSupport._
import effectie.concurrent.ExecutorServiceOps
object MyApp {
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatalEitherT(
for {
b <- EitherT(pureOf(divide100By(n)))
c <- eitherTRight[MyError](doSomethingBad(b))
} yield c
)(MyError.nonFatalThrowable).value
def main(args: Array[String]): Unit = {
val executorService: ExecutorService =
Executors.newWorkStealingPool(Runtime.getRuntime.availableProcessors())
implicit val ec: ExecutionContext = ExecutionContext.fromExecutorService(executorService)
try {
val fa = doSomething[Future](-1)
println(fa)
val result = Await.result(fa, 1.second)
println(result)
result match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
} finally {
ExecutorServiceOps.shutdownAndAwaitTermination(executorService, 1.second)
}
}
}
MyApp.main(Array.empty)
// Future(<not completed>)
// Left(NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100]))
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100])
Use of Id
is not recommended as Id
means having no Effect
. Use it only for some special cases like testing.
import cats._
import cats.data.EitherT
import cats.syntax.all._
import effectie.cats._
import effectie.cats.Effectful._
import effectie.cats.Catching._
import effectie.cats.EitherTSupport._
sealed trait MyError
object MyError {
final case class NonFatalThrowable(throwable: Throwable) extends MyError
case object DivideByZero extends MyError
def nonFatalThrowable(throwable: Throwable): MyError
= NonFatalThrowable(throwable)
def divideByZero: MyError = DivideByZero
}
def divide100By(n: Int): Either[MyError, Int] =
if (n === 0)
MyError.divideByZero.asLeft[Int]
else
(100 / n).asRight[MyError]
def doSomethingBad(n: Int): Int =
if (n < 0)
throw new IllegalArgumentException(s"n cannot be a negative number. [n: $n]")
else
n * 2
def doSomething[F[_]: Fx: CanCatch: Monad](
n: Int
): F[Either[MyError, Int]] =
catchNonFatalEitherT(
for {
b <- EitherT(pureOf(divide100By(n)))
c <- eitherTRight[MyError](doSomethingBad(b))
} yield c
)(MyError.nonFatalThrowable).value
val fa = doSomething[Id](-1)
// fa: Id[Either[MyError, Int]] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100]
// )
// )
fa match {
case Right(b) =>
println(s"Result is $b")
case Left(a) =>
println(s"Result: Failed with $a")
}
// Result: Failed with NonFatalThrowable(java.lang.IllegalArgumentException: n cannot be a negative number. [n: -100])