Skip to content

Commit 5d97f98

Browse files
backported #2863 Add attemptNarrow to ApplicativeErrorOps (#3289)
1 parent e37485f commit 5d97f98

File tree

2 files changed

+45
-0
lines changed

2 files changed

+45
-0
lines changed

core/src/main/scala/cats/syntax/applicativeError.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package syntax
44
import cats.data.Validated.{Invalid, Valid}
55
import cats.data.{EitherT, Validated}
66

7+
import scala.reflect.ClassTag
8+
79
trait ApplicativeErrorSyntax {
810
implicit final def catsSyntaxApplicativeErrorId[E](e: E): ApplicativeErrorIdOps[E] =
911
new ApplicativeErrorIdOps(e)
@@ -86,6 +88,12 @@ final class ApplicativeErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal
8688
def attemptT(implicit F: ApplicativeError[F, E]): EitherT[F, E, A] =
8789
F.attemptT(fa)
8890

91+
/**
92+
* Similar to [[attempt]], but it only handles errors of type `EE`.
93+
*/
94+
def attemptNarrow[EE <: E: ClassTag](implicit F: ApplicativeError[F, E]): F[Either[EE, A]] =
95+
F.recover(F.map(fa)(Right[EE, A](_): Either[EE, A])) { case e: EE => Left[EE, A](e) }
96+
8997
def recover(pf: PartialFunction[E, A])(implicit F: ApplicativeError[F, E]): F[A] =
9098
F.recover(fa)(pf)
9199

tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,43 @@ class ApplicativeErrorSuite extends CatsSuite {
2727
failed.attemptT should ===(EitherT[Option, Unit, Int](Option(Left(()))))
2828
}
2929

30+
test("attemptNarrow[EE] syntax creates an F[Either[EE, A]]") {
31+
trait Err
32+
case class ErrA() extends Err
33+
case class ErrB() extends Err
34+
35+
implicit val eqForErr: Eq[Err] = Eq.fromUniversalEquals[Err]
36+
implicit val eqForErrA: Eq[ErrA] = Eq.fromUniversalEquals[ErrA]
37+
implicit val eqForErrB: Eq[ErrB] = Eq.fromUniversalEquals[ErrB]
38+
39+
val failed: Either[Err, Int] = ErrA().raiseError[Either[Err, *], Int]
40+
41+
failed.attemptNarrow[ErrA] should ===(ErrA().asLeft[Int].asRight[Err])
42+
failed.attemptNarrow[ErrB] should ===(Either.left[Err, Either[ErrB, Int]](ErrA()))
43+
}
44+
45+
test("attemptNarrow works for parametrized types") {
46+
trait T[A]
47+
case object Str extends T[String]
48+
case class Num(i: Int) extends T[Int]
49+
50+
implicit def eqForT[A]: Eq[T[A]] = Eq.fromUniversalEquals[T[A]]
51+
implicit val eqForStr: Eq[Str.type] = Eq.fromUniversalEquals[Str.type]
52+
implicit val eqForNum: Eq[Num] = Eq.fromUniversalEquals[Num]
53+
54+
val e: Either[T[Int], Unit] = Num(1).asLeft[Unit]
55+
e.attemptNarrow[Num] should ===(e.asRight[T[Int]])
56+
assertTypeError("e.attemptNarrow[Str.type]")
57+
58+
val e2: Either[T[String], Unit] = Str.asLeft[Unit]
59+
e2.attemptNarrow[Str.type] should ===(e2.asRight[T[String]])
60+
assertTypeError("e2.attemptNarrow[Num]")
61+
62+
val e3: Either[List[T[String]], Unit] = List(Str).asLeft[Unit]
63+
e3.attemptNarrow[List[Str.type]] should ===(e3.asRight[List[T[String]]])
64+
assertTypeError("e3.attemptNarrow[List[Num]]")
65+
}
66+
3067
test("recover syntax transforms an error to a success") {
3168
failed.recover { case _ => 7 } should ===(Some(7))
3269
}

0 commit comments

Comments
 (0)