Skip to main content
Version: 2.0.0-beta13

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.

CanCatch looks like this.

trait CanCatch[F[_]] {
def catchNonFatal[A, B](fb: => F[B])(f: PartialFunction[Throwable, A]): F[Either[A, B]]

def catchNonFatalEither[A, AA >: A, B](fab: => F[Either[A, B]])(
f: PartialFunction[Throwable, AA]
): F[Either[AA, B]]
}

In practice, you don't need to use it directly because Fx is already CanCatch as well.

CanCatch.catchNonFatal

val fa: F[A] = ...
Fx[F].catchNonFatal(fa) {
case SomeException(message) =>
SomeError(message)
} // F[Either[SomeError, A]

CanCatch[F].catchNonFatal[A, B] lets you catch NonFatal Throwable from F[B] and returns F[Either[A, B]].

How to Use

import cats.effect._

import effectie.core._
import effectie.instances.ce2.fx._

final case class MyException(cause: Throwable) extends RuntimeException

val fa = CanCatch[IO].catchNonFatal(
IO(throw new RuntimeException("Something's wrong!"))
) {
case ex =>
MyException(ex)
}
// fa: IO[Either[MyException, Nothing]] = Bind(
// source = Bind(
// source = Delay(
// ...

fa.unsafeRunSync()
// res1: Either[MyException, Nothing] = Left(
// value = MyException(cause = java.lang.RuntimeException: Something's wrong!)
// )

Happy Path Example

import cats._
import cats.syntax.all._
import cats.effect._

import effectie.core._
import effectie.syntax.all._

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
) {
case ex => MyError.nonFatalThrowable(ex)
}

import effectie.instances.ce2.fx._

val fa = doSomething[IO](1)
// fa: IO[Either[MyError, Int]] = Bind(
// source = Bind(
// source = Bind(
// ...
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

Unhappy Path Example

import cats._
import cats.syntax.all._
import cats.effect._

import effectie.core._
import effectie.syntax.all._

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
) {
case ex => MyError.nonFatalThrowable(ex)
}

import effectie.instances.ce2.fx._

val fa = doSomething[IO](-101)
// fa: IO[Either[MyError, Int]] = Bind(
// source = Bind(
// source = Bind(
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative numbe...
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]

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

import cats.effect._

import effectie.core._
import effectie.instances.ce2.fx._

final case class MyException(cause: Throwable) extends RuntimeException

val fa = CanCatch[IO].catchNonFatalEither(
IO((throw new RuntimeException("Something's wrong!")): Either[Throwable, Int])
) {
case ex => MyException(ex)
}
// fa: IO[Either[Throwable, Int]] = Bind(
// source = Bind(
// source = Delay(
// ...

fa.unsafeRunSync()
// res19: Either[Throwable, Int] = Left(
// value = MyException(cause = java.lang.RuntimeException: Something's wrong!)
// )

Happy Path Example

import cats._
import cats.syntax.all._
import cats.effect._

import effectie.core._
import effectie.syntax.all._

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
) {
case ex => MyError.nonFatalThrowable(ex)
}

import effectie.instances.ce2.fx._

val fa = doSomething[IO](1)
// fa: IO[Either[MyError, Int]] = Bind(
// source = Bind(
// source = Bind(
// ...
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

Unhappy Path Example

import cats._
import cats.syntax.all._
import cats.effect._

import effectie.core._
import effectie.syntax.all._

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
) {
case ex => MyError.nonFatalThrowable(ex)
}

import effectie.instances.ce2.fx._

val fa = doSomething[IO](-1)
// fa: IO[Either[MyError, Int]] = Bind(
// source = Bind(
// source = Bind(
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative numbe...
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])

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

import cats.data.EitherT
import cats.effect._

import effectie.core._
import effectie.syntax.all._
import effectie.instances.ce2.fx._

final case class MyException(cause: Throwable) extends RuntimeException

val fa = CanCatch[IO].catchNonFatalEitherT(
EitherT(IO((throw new RuntimeException("Something's wrong!")): Either[Throwable, Int]))
) {
case ex => MyException(ex)
}
// fa: EitherT[[α$0$]IO[α$0$], Throwable, Int] = EitherT(
// value = Bind(
// source = Bind(
// ...

fa.value.unsafeRunSync()
// res37: Either[Throwable, Int] = Left(
// value = MyException(cause = java.lang.RuntimeException: Something's wrong!)
// )

Happy Path Example

import cats._
import cats.syntax.all._
import cats.effect._

import effectie.core._
import effectie.syntax.all._

import extras.cats.syntax.all._

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 <- pureOf(divide100By(n)).eitherT
c <- doSomethingBad(b).rightTF[F, MyError]
} yield c
) {
case ex => MyError.nonFatalThrowable(ex)
}.value

import effectie.instances.ce2.fx._

val fa = doSomething[IO](1)
// fa: IO[Either[MyError, Int]] = Bind(
// source = Bind(
// source = Bind(
// ...
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

Unhappy Path Example

import cats._
import cats.syntax.all._
import cats.effect._

import effectie.core._
import effectie.syntax.all._
import extras.cats.syntax.all._

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 <- pureOf(divide100By(n)).eitherT
c <- doSomethingBad(b).rightTF[F, MyError]
} yield c
) {
case ex => MyError.nonFatalThrowable(ex)
}.value

import effectie.instances.ce2.fx._

val fa = doSomething[IO](-1)
// fa: IO[Either[MyError, Int]] = Bind(
// source = Bind(
// source = Bind(
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative numbe...
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])

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

import cats.effect._

import effectie.syntax.all._

final case class MyException(cause: Throwable) extends RuntimeException

import effectie.instances.ce2.fx._

val fa =
IO(throw new RuntimeException("Something's wrong!"))
.catchNonFatal {
case ex => MyException(ex)
}
// fa: IO[Either[MyException, Nothing]] = Bind(
// source = Bind(
// source = Delay(
// ...

fa.unsafeRunSync()
// res55: Either[MyException, Nothing] = Left(
// value = MyException(cause = java.lang.RuntimeException: Something's wrong!)
// )

Happy Path Example

import cats._
import cats.syntax.all._
import cats.effect._

import effectie.core._
import effectie.syntax.all._

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]] =
(for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b)
.catchNonFatal {
case ex => MyError.nonFatalThrowable(ex)
}

import effectie.instances.ce2.fx._

val fa = doSomething[IO](1)
// fa: IO[Either[MyError, Int]] = Bind(
// source = Bind(
// source = Bind(
// ...
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

Unhappy Path Example

import cats._
import cats.syntax.all._
import cats.effect._

import effectie.core._
import effectie.syntax.all._

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]] =
(for {
a <- pureOf(n + 100)
b <- effectOf(doSomethingBad(a))
} yield b)
.catchNonFatal {
case ex => MyError.nonFatalThrowable(ex)
}

import effectie.instances.ce2.fx._

val fa = doSomething[IO](-101)
// fa: IO[Either[MyError, Int]] = Bind(
// source = Bind(
// source = Bind(
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative numbe...
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]

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

import cats.effect._

import effectie.syntax.all._

final case class MyException(cause: Throwable) extends RuntimeException

import effectie.instances.ce2.fx._

val fa =
IO((throw new RuntimeException("Something's wrong!")): Either[Throwable, Int])
.catchNonFatalEither {
case ex => MyException(ex)
}
// fa: IO[Either[Throwable, Int]] = Bind(
// source = Bind(
// source = Delay(
// ...

fa.unsafeRunSync()
// res72: Either[Throwable, Int] = Left(
// value = MyException(cause = java.lang.RuntimeException: Something's wrong!)
// )

Happy Path Example

import cats._
import cats.syntax.all._
import cats.effect._

import effectie.core._
import effectie.syntax.all._

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]] =
(for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c)
.catchNonFatalEither {
case ex => MyError.nonFatalThrowable(ex)
}

import effectie.instances.ce2.fx._

val fa = doSomething[IO](1)
// fa: IO[Either[MyError, Int]] = Bind(
// source = Bind(
// source = Bind(
// ...
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

Unhappy Path Example

import cats._
import cats.syntax.all._
import cats.effect._

import effectie.core._
import effectie.syntax.all._

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]] =
(for {
aOrB <- pureOf(divide100By(n))
c <- effectOf(aOrB.map(b => doSomethingBad(b)))
} yield c)
.catchNonFatalEither {
case ex => MyError.nonFatalThrowable(ex)
}

import effectie.instances.ce2.fx._

val fa = doSomething[IO](-1)
// fa: IO[Either[MyError, Int]] = Bind(
// source = Bind(
// source = Bind(
// ...
val result = fa.unsafeRunSync()
// result: Either[MyError, Int] = Left(
// value = NonFatalThrowable(
// throwable = java.lang.IllegalArgumentException: n cannot be a negative numbe...
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])