From 8cfb9c0e8d88646fae821e191043d9a0a5c67be9 Mon Sep 17 00:00:00 2001 From: Mateusz Kubuszok Date: Thu, 22 Feb 2024 14:29:43 +0100 Subject: [PATCH 1/2] Prefix all cats instances with cats, add Alternative instance for partial.Result and PartialTransformer, change instances name to something which won't change as often --- .../cats/CatsPartialResultImplicits.scala | 56 +++++++++++-------- .../CatsPartialTransformerImplicits.scala | 28 +++++++--- .../cats/CatsTotalTransformerImplicits.scala | 6 +- .../chimney/cats/PartialResultLaws.scala | 3 +- .../chimney/cats/PartialTransformerLaws.scala | 8 ++- .../chimney/cats/utils/ArbitraryUtils.scala | 2 +- 6 files changed, 63 insertions(+), 40 deletions(-) diff --git a/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsPartialResultImplicits.scala b/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsPartialResultImplicits.scala index 0fb0343c3..6099cf621 100644 --- a/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsPartialResultImplicits.scala +++ b/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsPartialResultImplicits.scala @@ -1,11 +1,11 @@ package io.scalaland.chimney.cats -import _root_.cats.{~>, Applicative, CoflatMap, Eval, Monad, MonadError, Parallel, Traverse} +import _root_.cats.{~>, Alternative, Applicative, CoflatMap, Eval, Monad, MonadError, Parallel, Traverse} import _root_.cats.arrow.FunctionK import _root_.cats.data.{Chain, NonEmptyChain, NonEmptyList, Validated, ValidatedNec, ValidatedNel} import _root_.cats.kernel.{Eq, Semigroup} import io.scalaland.chimney.partial -import io.scalaland.chimney.partial.{AsResult, Result} +import io.scalaland.chimney.partial.AsResult import language.implicitConversions @@ -13,9 +13,14 @@ import language.implicitConversions trait CatsPartialResultImplicits { /** @since 0.7.0 */ - implicit final val monadErrorCoflatMapTraversePartialResult - : MonadError[partial.Result, partial.Result.Errors] & CoflatMap[partial.Result] & Traverse[partial.Result] = - new MonadError[partial.Result, partial.Result.Errors] with CoflatMap[partial.Result] with Traverse[partial.Result] { + implicit final val catsCovariantForPartialResult: MonadError[partial.Result, partial.Result.Errors] & + CoflatMap[partial.Result] & + Traverse[partial.Result] & + Alternative[partial.Result] = + new MonadError[partial.Result, partial.Result.Errors] + with CoflatMap[partial.Result] + with Traverse[partial.Result] + with Alternative[partial.Result] { override def pure[A](x: A): partial.Result[A] = partial.Result.Value(x) override def flatMap[A, B](fa: partial.Result[A])(f: A => partial.Result[B]): partial.Result[B] = fa.flatMap(f) @@ -55,10 +60,14 @@ trait CatsPartialResultImplicits { case partial.Result.Value(value) => f(value, lb) case _ => lb } + + override def empty[A]: partial.Result[A] = partial.Result.fromEmpty[A] + + override def combineK[A](x: partial.Result[A], y: partial.Result[A]): partial.Result[A] = x.orElse(y) } /** @since 1.0.0 */ - implicit final val parallelSemigroupalPartialResult: Parallel[partial.Result] { + implicit final val catsParallelForPartialResult: Parallel[partial.Result] { type F[A] = partial.Result[A] } = new Parallel[partial.Result] { override type F[A] = partial.Result[A] @@ -73,23 +82,22 @@ trait CatsPartialResultImplicits { partial.Result.map2[A => B, A, B](ff, fa, (f, a) => f(a), failFast = false) } - override val monad: Monad[partial.Result] = monadErrorCoflatMapTraversePartialResult + override val monad: Monad[partial.Result] = catsCovariantForPartialResult } /** @since 0.7.0 */ - implicit final val semigroupPartialResultErrors: Semigroup[partial.Result.Errors] = + implicit final val catsSemigroupForPartialResultErrors: Semigroup[partial.Result.Errors] = Semigroup.instance(partial.Result.Errors.merge) /** @since 1.0.0 */ - implicit final def eqPartialResult[A: Eq]: Eq[partial.Result[A]] = { - case (partial.Result.Value(a1), partial.Result.Value(a2)) => Eq[A].eqv(a1, a2) - case (e1: partial.Result.Errors, e2: partial.Result.Errors) => - e1.asErrorPathMessages.iterator.sameElements(e2.asErrorPathMessages.iterator) - case _ => false + implicit final def catsEqForPartialResult[A: Eq]: Eq[partial.Result[A]] = { + case (partial.Result.Value(a1), partial.Result.Value(a2)) => Eq[A].eqv(a1, a2) + case (e1: partial.Result.Errors, e2: partial.Result.Errors) => catsEqForPartialResultErrors.eqv(e1, e2) + case _ => false } /** @since 1.0.0 */ - implicit final def eqPartialResultErrors: Eq[partial.Result.Errors] = (e1, e2) => + implicit final val catsEqForPartialResultErrors: Eq[partial.Result.Errors] = (e1, e2) => e1.asErrorPathMessages.iterator.sameElements(e2.asErrorPathMessages.iterator) /** @since 0.7.0 */ @@ -97,45 +105,45 @@ trait CatsPartialResultImplicits { new CatsPartialTransformerResultOps(ptr) /** @since 1.0.0 */ - implicit def validatedPartialResultErrorsAsResult[E <: partial.Result.Errors]: AsResult[Validated[E, *]] = + implicit def catsValidatedPartialResultErrorsAsResult[E <: partial.Result.Errors]: AsResult[Validated[E, *]] = new AsResult[Validated[E, *]] { - def asResult[A](fa: Validated[E, A]): Result[A] = fa match { + def asResult[A](fa: Validated[E, A]): partial.Result[A] = fa match { case Validated.Valid(a) => partial.Result.fromValue(a) case Validated.Invalid(e) => e } } /** @since 1.0.0 */ - implicit def validatedNecPartialErrorAsResult[E <: partial.Error]: AsResult[ValidatedNec[E, *]] = + implicit def catsValidatedNecPartialErrorAsResult[E <: partial.Error]: AsResult[ValidatedNec[E, *]] = new AsResult[ValidatedNec[E, *]] { - def asResult[A](fa: ValidatedNec[E, A]): Result[A] = fa match { + def asResult[A](fa: ValidatedNec[E, A]): partial.Result[A] = fa match { case Validated.Valid(a) => partial.Result.fromValue(a) case Validated.Invalid(e) => partial.Result.Errors(e.head, e.tail.toList*) } } /** @since 1.0.0 */ - implicit def validatedNelPartialErrorAsResult[E <: partial.Error]: AsResult[ValidatedNel[E, *]] = + implicit def catsValidatedNelPartialErrorAsResult[E <: partial.Error]: AsResult[ValidatedNel[E, *]] = new AsResult[ValidatedNel[E, *]] { - def asResult[A](fa: ValidatedNel[E, A]): Result[A] = fa match { + def asResult[A](fa: ValidatedNel[E, A]): partial.Result[A] = fa match { case Validated.Valid(a) => partial.Result.fromValue(a) case Validated.Invalid(e) => partial.Result.Errors(e.head, e.tail*) } } /** @since 1.0.0 */ - implicit def validatedNecStringAsResult[E <: String]: AsResult[ValidatedNec[E, *]] = + implicit def catsValidatedNecStringAsResult[E <: String]: AsResult[ValidatedNec[E, *]] = new AsResult[ValidatedNec[E, *]] { - def asResult[A](fa: ValidatedNec[E, A]): Result[A] = fa match { + def asResult[A](fa: ValidatedNec[E, A]): partial.Result[A] = fa match { case Validated.Valid(a) => partial.Result.fromValue(a) case Validated.Invalid(e) => partial.Result.fromErrorStrings(e.head, e.tail.toList*) } } /** @since 1.0.0 */ - implicit def validatedNelStringAsResult[E <: String]: AsResult[ValidatedNel[E, *]] = + implicit def catsValidatedNelStringAsResult[E <: String]: AsResult[ValidatedNel[E, *]] = new AsResult[ValidatedNel[E, *]] { - def asResult[A](fa: ValidatedNel[E, A]): Result[A] = fa match { + def asResult[A](fa: ValidatedNel[E, A]): partial.Result[A] = fa match { case Validated.Valid(a) => partial.Result.fromValue(a) case Validated.Invalid(e) => partial.Result.fromErrorStrings(e.head, e.tail*) } diff --git a/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsPartialTransformerImplicits.scala b/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsPartialTransformerImplicits.scala index a3cadca73..cf51a85db 100644 --- a/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsPartialTransformerImplicits.scala +++ b/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsPartialTransformerImplicits.scala @@ -1,6 +1,6 @@ package io.scalaland.chimney.cats -import _root_.cats.{~>, Applicative, CoflatMap, Contravariant, Monad, MonadError, Parallel} +import _root_.cats.{~>, Alternative, Applicative, CoflatMap, Contravariant, Monad, MonadError, Parallel} import _root_.cats.arrow.{ArrowChoice, CommutativeArrow, FunctionK} import io.scalaland.chimney.partial import io.scalaland.chimney.PartialTransformer @@ -9,7 +9,7 @@ import io.scalaland.chimney.PartialTransformer trait CatsPartialTransformerImplicits { /** @since 1.0.0 */ - implicit final val commutativeArrowChoiceForPartialTransformer + implicit final val catsCategoryForPartialTransformer : ArrowChoice[PartialTransformer] & CommutativeArrow[PartialTransformer] = new ArrowChoice[PartialTransformer] with CommutativeArrow[PartialTransformer] { override def lift[A, B](f: A => B): PartialTransformer[A, B] = PartialTransformer.fromFunction(f) @@ -35,9 +35,13 @@ trait CatsPartialTransformerImplicits { } /** @since 1.0.0 */ - implicit final def monadErrorCoflatMapForPartialTransformer[Source] - : MonadError[PartialTransformer[Source, *], partial.Result.Errors] & CoflatMap[PartialTransformer[Source, *]] = - new MonadError[PartialTransformer[Source, *], partial.Result.Errors] with CoflatMap[PartialTransformer[Source, *]] { + implicit final def catsCovariantForPartialTransformer[Source] + : MonadError[PartialTransformer[Source, *], partial.Result.Errors] & + CoflatMap[PartialTransformer[Source, *]] & + Alternative[PartialTransformer[Source, *]] = + new MonadError[PartialTransformer[Source, *], partial.Result.Errors] + with CoflatMap[PartialTransformer[Source, *]] + with Alternative[PartialTransformer[Source, *]] { override def pure[A](x: A): PartialTransformer[Source, A] = (_, _) => partial.Result.Value(x) override def flatMap[A, B](fa: PartialTransformer[Source, A])( @@ -78,10 +82,18 @@ trait CatsPartialTransformerImplicits { )( f: PartialTransformer[Source, A] => B ): PartialTransformer[Source, B] = (src, _) => partial.Result.fromCatching(f(fa)) + + override def empty[A]: PartialTransformer[Source, A] = (_, _) => partial.Result.fromEmpty[A] + + override def combineK[A]( + x: PartialTransformer[Source, A], + y: PartialTransformer[Source, A] + ): PartialTransformer[Source, A] = (src, failFast) => + x.transform(src, failFast).orElse(y.transform(src, failFast)) } /** @since 1.0.0 */ - implicit final def parallelForPartialTransformer[Source]: Parallel[PartialTransformer[Source, *]] { + implicit final def catsParallelForPartialTransformer[Source]: Parallel[PartialTransformer[Source, *]] { type F[A] = PartialTransformer[Source, A] } = new Parallel[PartialTransformer[Source, *]] { @@ -105,11 +117,11 @@ trait CatsPartialTransformerImplicits { ) } - val monad: Monad[PartialTransformer[Source, *]] = monadErrorCoflatMapForPartialTransformer[Source] + val monad: Monad[PartialTransformer[Source, *]] = catsCovariantForPartialTransformer[Source] } /** @since 1.0.0 */ - implicit final def contravariantForPartialTransformer[Target]: Contravariant[PartialTransformer[*, Target]] = + implicit final def catsContravariantForPartialTransformer[Target]: Contravariant[PartialTransformer[*, Target]] = new Contravariant[PartialTransformer[*, Target]] { def contramap[A, B](fa: PartialTransformer[A, Target])(f: B => A): PartialTransformer[B, Target] = (b, failFast) => partial.Result.fromCatching(f(b)).flatMap(a => fa.transform(a, failFast)) diff --git a/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsTotalTransformerImplicits.scala b/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsTotalTransformerImplicits.scala index 5fb154795..e16245a9e 100644 --- a/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsTotalTransformerImplicits.scala +++ b/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsTotalTransformerImplicits.scala @@ -7,7 +7,7 @@ import io.scalaland.chimney.Transformer trait CatsTotalTransformerImplicits { /** @since 1.0.0 */ - implicit final val commutativeArrowChoiceForTransformer: ArrowChoice[Transformer] & CommutativeArrow[Transformer] = + implicit final val catsCategoryForTransformer: ArrowChoice[Transformer] & CommutativeArrow[Transformer] = new ArrowChoice[Transformer] with CommutativeArrow[Transformer] { override def lift[A, B](f: A => B): Transformer[A, B] = f(_) @@ -28,7 +28,7 @@ trait CatsTotalTransformerImplicits { } /** @since 1.0.0 */ - implicit final def monadCoflatMapForTransformer[Source] + implicit final def catsCovariantForTransformer[Source] : Monad[Transformer[Source, *]] & CoflatMap[Transformer[Source, *]] = new Monad[Transformer[Source, *]] with CoflatMap[Transformer[Source, *]] { override def pure[A](x: A): Transformer[Source, A] = _ => x @@ -56,7 +56,7 @@ trait CatsTotalTransformerImplicits { } /** @since 1.0.0 */ - implicit final def contravariantForTransformer[Target]: Contravariant[Transformer[*, Target]] = + implicit final def catsContravariantForTransformer[Target]: Contravariant[Transformer[*, Target]] = new Contravariant[Transformer[*, Target]] { def contramap[A, B](fa: Transformer[A, Target])(f: B => A): Transformer[B, Target] = b => fa.transform(f(b)) diff --git a/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialResultLaws.scala b/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialResultLaws.scala index d9e229552..f527e7ae4 100644 --- a/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialResultLaws.scala +++ b/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialResultLaws.scala @@ -12,7 +12,7 @@ class PartialResultLaws extends ChimneySpec with utils.ArbitraryUtils { } group( - "MonadError[partial.Result, partial.Result.Errors] & CoflatMap[partial.Result] & Traverse[partial.Result] should follow laws" + "MonadError[partial.Result, partial.Result.Errors] & CoflatMap[partial.Result] & Traverse[partial.Result] & Alternative[partial.Result] should follow laws" ) { checkLawsAsTests(InvariantTests[partial.Result].invariant[Int, String, Double]) checkLawsAsTests(SemigroupalTests[partial.Result].semigroupal[Int, String, Double]) @@ -29,5 +29,6 @@ class PartialResultLaws extends ChimneySpec with utils.ArbitraryUtils { UnorderedTraverseTests[partial.Result].unorderedTraverse[Int, Long, Double, Const[Unit, *], Const[Int, *]] ) checkLawsAsTests(TraverseTests[partial.Result].traverse[Int, Long, Double, Byte, Const[Unit, *], Const[Int, *]]) + checkLawsAsTests(AlternativeTests[partial.Result].alternative[Int, String, Double]) } } diff --git a/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialTransformerLaws.scala b/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialTransformerLaws.scala index 27401b5fa..2e37023c9 100644 --- a/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialTransformerLaws.scala +++ b/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialTransformerLaws.scala @@ -17,9 +17,10 @@ class PartialTransformerLaws extends ChimneySpec with utils.ArbitraryUtils { } group( - "MonadError[Transformer[Source, *], partial.Result.Errors] & CoflatMap[Transformer[Source, *]] instance should follow laws" + "MonadError[PartialTransformer[Source, *], partial.Result.Errors] & CoflatMap[PartialTransformer[Source, *]] & Alternative[PartialTransformer[Source, *]] instance should follow laws" ) { checkLawsAsTests(InvariantTests[PartialTransformer[String, *]].invariant[Int, String, Double]) + checkLawsAsTests(SemigroupalTests[PartialTransformer[String, *]].semigroupal[Int, String, Double]) checkLawsAsTests(FunctorTests[PartialTransformer[String, *]].functor[Int, String, Double]) checkLawsAsTests(ApplicativeTests[PartialTransformer[String, *]].applicative[Int, String, Double]) checkLawsAsTests(FlatMapTests[PartialTransformer[String, *]].flatMap[Int, String, Double]) @@ -31,14 +32,15 @@ class PartialTransformerLaws extends ChimneySpec with utils.ArbitraryUtils { MonadErrorTests[PartialTransformer[String, *], partial.Result.Errors].monadError[Int, String, Double] ) checkLawsAsTests(CoflatMapTests[PartialTransformer[String, *]].coflatMap[Int, String, Double]) + checkLawsAsTests(AlternativeTests[PartialTransformer[String, *]].alternative[Int, String, Double]) } - group("Parallel[Transformer[From, *]] instance should follow laws") { + group("Parallel[PartialTransformer[From, *]] instance should follow laws") { checkLawsAsTests(NonEmptyParallelTests[PartialTransformer[String, *]].nonEmptyParallel[Int, String]) checkLawsAsTests(ParallelTests[PartialTransformer[String, *]].parallel[Int, String]) } - group("Contravariant[Transformer[*, To]] instance should follow laws") { + group("Contravariant[PartialTransformer[*, To]] instance should follow laws") { checkLawsAsTests(ContravariantTests[PartialTransformer[*, String]].contravariant[Int, String, Double]) } } diff --git a/chimney-cats/src/test/scala/io/scalaland/chimney/cats/utils/ArbitraryUtils.scala b/chimney-cats/src/test/scala/io/scalaland/chimney/cats/utils/ArbitraryUtils.scala index 7e37ba05b..40ebfe69f 100644 --- a/chimney-cats/src/test/scala/io/scalaland/chimney/cats/utils/ArbitraryUtils.scala +++ b/chimney-cats/src/test/scala/io/scalaland/chimney/cats/utils/ArbitraryUtils.scala @@ -4,7 +4,7 @@ import cats.Eq import cats.data.Const import cats.syntax.eq.* import io.scalaland.chimney.{partial, ChimneySpec, PartialTransformer, Transformer} -import io.scalaland.chimney.cats.eqPartialResult +import io.scalaland.chimney.cats.catsEqForPartialResult import org.scalacheck.{Arbitrary, Cogen} import org.scalacheck.Test.check import org.scalacheck.Prop.forAll From 1e2a963893bff1c74adb2942179812e48e984c6b Mon Sep 17 00:00:00 2001 From: Mateusz Kubuszok Date: Tue, 5 Mar 2024 14:00:49 +0100 Subject: [PATCH 2/2] Test and document result.orElse, update Cats typeclasses docs, rename category implicits to arrow implicits --- .../CatsPartialTransformerImplicits.scala | 2 +- .../cats/CatsTotalTransformerImplicits.scala | 2 +- .../chimney/cats/PartialResultLaws.scala | 2 ++ .../chimney/cats/PartialTransformerLaws.scala | 2 ++ .../io/scalaland/chimney/partial/Result.scala | 18 +++++++++++ .../scalaland/chimney/PartialResultSpec.scala | 32 +++++++++++++++++++ docs/docs/cookbook.md | 13 ++++---- 7 files changed, 63 insertions(+), 8 deletions(-) diff --git a/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsPartialTransformerImplicits.scala b/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsPartialTransformerImplicits.scala index cf51a85db..177846415 100644 --- a/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsPartialTransformerImplicits.scala +++ b/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsPartialTransformerImplicits.scala @@ -9,7 +9,7 @@ import io.scalaland.chimney.PartialTransformer trait CatsPartialTransformerImplicits { /** @since 1.0.0 */ - implicit final val catsCategoryForPartialTransformer + implicit final val catsArrowForPartialTransformer : ArrowChoice[PartialTransformer] & CommutativeArrow[PartialTransformer] = new ArrowChoice[PartialTransformer] with CommutativeArrow[PartialTransformer] { override def lift[A, B](f: A => B): PartialTransformer[A, B] = PartialTransformer.fromFunction(f) diff --git a/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsTotalTransformerImplicits.scala b/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsTotalTransformerImplicits.scala index e16245a9e..8b48e4a1b 100644 --- a/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsTotalTransformerImplicits.scala +++ b/chimney-cats/src/main/scala/io/scalaland/chimney/cats/CatsTotalTransformerImplicits.scala @@ -7,7 +7,7 @@ import io.scalaland.chimney.Transformer trait CatsTotalTransformerImplicits { /** @since 1.0.0 */ - implicit final val catsCategoryForTransformer: ArrowChoice[Transformer] & CommutativeArrow[Transformer] = + implicit final val catsArrowForTransformer: ArrowChoice[Transformer] & CommutativeArrow[Transformer] = new ArrowChoice[Transformer] with CommutativeArrow[Transformer] { override def lift[A, B](f: A => B): Transformer[A, B] = f(_) diff --git a/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialResultLaws.scala b/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialResultLaws.scala index f527e7ae4..7aeb95bb0 100644 --- a/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialResultLaws.scala +++ b/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialResultLaws.scala @@ -29,6 +29,8 @@ class PartialResultLaws extends ChimneySpec with utils.ArbitraryUtils { UnorderedTraverseTests[partial.Result].unorderedTraverse[Int, Long, Double, Const[Unit, *], Const[Int, *]] ) checkLawsAsTests(TraverseTests[partial.Result].traverse[Int, Long, Double, Byte, Const[Unit, *], Const[Int, *]]) + checkLawsAsTests(SemigroupKTests[partial.Result].semigroupK[Int]) + checkLawsAsTests(MonoidKTests[partial.Result].monoidK[Int]) checkLawsAsTests(AlternativeTests[partial.Result].alternative[Int, String, Double]) } } diff --git a/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialTransformerLaws.scala b/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialTransformerLaws.scala index 2e37023c9..a9aaf20cd 100644 --- a/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialTransformerLaws.scala +++ b/chimney-cats/src/test/scala/io/scalaland/chimney/cats/PartialTransformerLaws.scala @@ -32,6 +32,8 @@ class PartialTransformerLaws extends ChimneySpec with utils.ArbitraryUtils { MonadErrorTests[PartialTransformer[String, *], partial.Result.Errors].monadError[Int, String, Double] ) checkLawsAsTests(CoflatMapTests[PartialTransformer[String, *]].coflatMap[Int, String, Double]) + checkLawsAsTests(SemigroupKTests[PartialTransformer[String, *]].semigroupK[Int]) + checkLawsAsTests(MonoidKTests[PartialTransformer[String, *]].monoidK[Int]) checkLawsAsTests(AlternativeTests[PartialTransformer[String, *]].alternative[Int, String, Double]) } diff --git a/chimney/src/main/scala/io/scalaland/chimney/partial/Result.scala b/chimney/src/main/scala/io/scalaland/chimney/partial/Result.scala index 8efee671d..e6734b2ff 100644 --- a/chimney/src/main/scala/io/scalaland/chimney/partial/Result.scala +++ b/chimney/src/main/scala/io/scalaland/chimney/partial/Result.scala @@ -140,6 +140,24 @@ sealed trait Result[+A] { case _: Result.Errors => this.asInstanceOf[Result[B]] } + /** Prepends a [[io.scalaland.chimney.partial.PathElement]] to all errors represented by this result. + * + * @tparam B the element type of the returned result + * @param result lazy [[io.scalaland.chimney.partial.Result]] to compute as a fallback if this one has errors + * @return a [[io.scalaland.chimney.partial.Result]] with the first successful value or a failure combining errors + * from both results + * + * @since 1.0.0 + */ + final def orElse[B >: A](result: => Result[B]): Result[B] = this match { + case _: Result.Value[?] => this + case e: Result.Errors => + result match { + case r: Result.Value[?] => r + case e2: Result.Errors => Result.Errors.merge(e, e2) + } + } + /** Prepends a [[io.scalaland.chimney.partial.PathElement]] to all errors represented by this result. * * @param pathElement [[io.scalaland.chimney.partial.PathElement]] to be prepended diff --git a/chimney/src/test/scala/io/scalaland/chimney/PartialResultSpec.scala b/chimney/src/test/scala/io/scalaland/chimney/PartialResultSpec.scala index b15db24c0..2cbfe3d81 100644 --- a/chimney/src/test/scala/io/scalaland/chimney/PartialResultSpec.scala +++ b/chimney/src/test/scala/io/scalaland/chimney/PartialResultSpec.scala @@ -135,6 +135,38 @@ class PartialResultSpec extends ChimneySpec { result2.asErrorPathMessageStrings ==> Iterable("" -> """For input string: "error2"""") } + test("orElse fallbacks on another Result on error, aggregating errors if both Results fail") { + var used = false + val result = partial.Result.fromValue(1).orElse { + used = true + partial.Result.fromValue(2) + } + result.asOption ==> Some(1) + result.asEither ==> Right(1) + result.asErrorPathMessageStrings ==> Iterable() + used ==> false + + var used2 = false + val result2 = partial.Result.fromEmpty[Int].orElse { + used2 = true + partial.Result.fromValue(2) + } + result2.asOption ==> Some(2) + result2.asEither ==> Right(2) + result2.asErrorPathMessageStrings ==> Iterable() + used2 ==> true + + var used3 = false + val result3 = partial.Result.fromEmpty[Int].orElse { + used3 = true + partial.Result.fromEmpty[Int] + } + result3.asOption ==> None + result3.asEither.isLeft ==> true + result3.asErrorPathMessageStrings ==> Iterable("" -> """empty value""", "" -> """empty value""") + used3 ==> true + } + test("fromFunction converts function into function returning Result") { partial.Result.fromFunction[Int, Int](_ * 2).apply(3) ==> partial.Result.fromValue(6) } diff --git a/docs/docs/cookbook.md b/docs/docs/cookbook.md index 02ad14345..c728a899e 100644 --- a/docs/docs/cookbook.md +++ b/docs/docs/cookbook.md @@ -295,20 +295,21 @@ Cats integration module contains the following utilities: - for `Transformer` type class: - `ArrowChoice[Transformer] & CommutativeArrow[Transformer]` (implementing also `Arrow`, `Choice`, `Category`, `Compose`, `Strong`, `Profunctor`) - - `[Source] => Monad[Transformer[Source, *]] with CoflatMap[Transformer[Source, *]]` (implementing also `Monad`, - `Applicative`, `Functor`) + - `[Source] => Monad[Transformer[Source, *]] & CoflatMap[Transformer[Source, *]]` + (implementing also `Monad`, `Applicative`, `Functor`) - `[Target] => Contravariant[Transformer[*, Target]]` (implementing also `Invariant`) - for `PartialTransformer` type class: - `ArrowChoice[PartialTransformer] & CommutativeArrow[PartialTransformer]` (implementing also `Arrow`, `Choice`, `Category`,`Compose`, `Strong`, `Profunctor`) - - `[Source] => MonadError[PartialTransformer[Source, *], partial.Result.Errors] with CoflatMap[PartialTransformer[Source, *]]` - (implementing also `Monad`, `Applicative`, `Functor`, `ApplicativeError`) + - `[Source] => MonadError[PartialTransformer[Source, *], partial.Result.Errors] & CoflatMap[PartialTransformer[Source, *]] & Alternative[PartialTransformer[Source, *]]` + (implementing also `Monad`, `Applicative`, `Functor`, `ApplicativeError`, `NonEmptyAlternative`, `MonoidK`, + `SemigroupK`) - `[Source] => Parallel[PartialTransformer[Source, *]]` (implementing also `NonEmptyParallel`) - `[Target] => Contravariant[Transformer[*, Target]]` (implementing also `Invariant`) - for `partial.Result` data type: - - `MonadError[partial.Result, partial.Result.Errors] & CoflatMap[partial.Result] & Traverse[partial.Result]` + - `MonadError[partial.Result, partial.Result.Errors] & CoflatMap[partial.Result] & Traverse[partial.Result] $ Alternative[partial.Result]` (implementing also `Monad`, `Applicative`, `Functor`, `ApplicativeError`, `UnorderedTraverse`, `Foldable`, - `UnorderedFoldable`, `Invariant[partial.Result]`, `Semigriupal[partial.Result]`, ...) + `UnorderedFoldable`, `Invariant`, `Semigriupal`, `NonEmptyAlternative`, `SemigroupK`, `MonoidK`) - `Parallel[partial.Result]` (implementing also`NonEmptyParallel`) - `Semigroup[partial.Result.Errors]`