From cdb88e79066e96084b328d0bf6dd7ada7ac8fe5f Mon Sep 17 00:00:00 2001 From: Stefanos Stefanou Date: Thu, 16 Nov 2023 12:11:00 +0000 Subject: [PATCH] Either Bimonad and tests --- diagram.drawio | 2 +- src/main/scala/mycats/instances/Either.scala | 20 ++- .../lib/morphisms/error/MonadError.scala | 5 +- .../scala/mycats/instances/EitherSpec.scala | 118 ++++++++++++++++++ ~$diagram.drawio.bkp | 1 + 5 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 src/test/scala/mycats/instances/EitherSpec.scala create mode 100644 ~$diagram.drawio.bkp diff --git a/diagram.drawio b/diagram.drawio index cdb7d32..9bd61c1 100644 --- a/diagram.drawio +++ b/diagram.drawio @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/src/main/scala/mycats/instances/Either.scala b/src/main/scala/mycats/instances/Either.scala index 2db67d5..2ae56f5 100644 --- a/src/main/scala/mycats/instances/Either.scala +++ b/src/main/scala/mycats/instances/Either.scala @@ -1,17 +1,10 @@ package mycats.instances +import mycats.lib.morphisms.bi.BiFunctor import mycats.lib.morphisms.error.MonadError import mycats.lib.obj.Semigroup object Either { - implicit def eitherMonadErrorInstance[E](implicit semiOfE:Semigroup[E]) = new MonadError[({type F[C] = Either[E,C]})#F,E] { - - override def ensure[A](fa: Either[E, A])(error: =>E)(predicate: A => Boolean): Either[E, A] = ensureOr(fa)(_=>error)(predicate) - - override def ensureOr[A](fa: Either[E, A])(error: A => E)(predicate: A => Boolean): Either[E, A] = fa match { - case p@Right(value) if predicate(value) => p - case Right(value) => Left(error(value)) - case p=>p - } + implicit def eitherMonadErrorInstance[E] = new MonadError[({type F[C] = Either[E,C]})#F,E] with BiFunctor[Either] { override def adaptError[A](fa: Either[E, A])(pf: PartialFunction[E, E]): Either[E, A] = fa match { case Left(value) => Left(pf.orElse[E,E]({case e=>e})(value)) @@ -36,10 +29,15 @@ object Either { case (Right(f),Right(a))=>Right(f(a)) case (Left(f),_)=>Left(f) case (_,Left(a))=>Left(a) - case (Left(f),Left(a))=>Left(semiOfE.combine(f,a)) } override def map[A, B](fa: Either[E, A])(f: A => B): Either[E, B] = flatMap(fa)(f andThen pure) - } + + override def bimap[A, B, C, D](fab: Either[A, B])(fac: A => C)(fbd: B => D): Either[C, D] = + fab match { + case Left(value) => Left(fac(value)) + case Right(value) => Right(fbd(value)) + } + } } diff --git a/src/main/scala/mycats/lib/morphisms/error/MonadError.scala b/src/main/scala/mycats/lib/morphisms/error/MonadError.scala index 5ab1421..c28a2da 100644 --- a/src/main/scala/mycats/lib/morphisms/error/MonadError.scala +++ b/src/main/scala/mycats/lib/morphisms/error/MonadError.scala @@ -2,8 +2,9 @@ package mycats.lib.morphisms.error import mycats.lib.morphisms.Monad trait MonadError[F[_],E] extends ApplicativeError[F,E] with Monad[F]{ - def ensure[A](fa:F[A])(error: =>E)(predicate:A=>Boolean):F[A] - def ensureOr[A](fa:F[A])(error: A=>E)(predicate:A=>Boolean):F[A] + def ensure[A](fa:F[A])(error: =>E)(predicate:A=>Boolean):F[A] = ??? + def ensureOr[A](fa:F[A])(error: A=>E)(predicate:A=>Boolean):F[A] = + flatMap[A,A](fa)(v => if(predicate(v)) pure(v) else raiseError[A](error(v))) def adaptError[A](fa:F[A])(pf:PartialFunction[E,E]):F[A] def rethrow[A,EE<:E](fa:F[Either[EE,A]]):F[A] diff --git a/src/test/scala/mycats/instances/EitherSpec.scala b/src/test/scala/mycats/instances/EitherSpec.scala new file mode 100644 index 0000000..f894542 --- /dev/null +++ b/src/test/scala/mycats/instances/EitherSpec.scala @@ -0,0 +1,118 @@ +package mycats.instances + +import mycats.algebras.ValidatedNel.ValidatedNel +import mycats.algebras.{Invalid, Valid} +import mycats.examples.common.Expense +import mycats.examples.common.Utils.NonEmptyList +import mycats.instances.ValidatedNel._ +import mycats.lib.syntax.ApplicativeErrorSyntax._ +import mycats.lib.syntax.ApplySyntax.{Tuple2ApplyOps, applySyntaxOps} +import mycats.lib.syntax.BiFunctorSyntax.BiFunctorSyntaxOps +import mycats.lib.syntax.FunctorSyntax.FunctorSyntaxOps +import mycats.lib.syntax.SemigroupSyntax._ +import mycats.lib.syntax.SemigroupalSyntax.SemigroupalSyntaxOps +import mycats.instances.Either._ +class EitherSpec extends org.scalatest.funsuite.AnyFunSuite { + + test("Either BiFunctor: bimap should transform valid context") { + val right:Either[String,Int] = Right(42) + assert(right.bimap(identity)(String.valueOf)==Right("42"),"Either BiFunctor: bimap should transform valid context") + } + test("Either BiFunctor: bimap should transform invalid context") { + val left:Either[String,Int] = Left("Error") + assert(left.bimap(identity)(String.valueOf)==Left("Error"),"Either BiFunctor: bimap should transform invalid context") + } + test("Either ApplicativeError: raiseError should lift E to Left[E,A] as invalid projection") { + assert(eitherMonadErrorInstance.raiseError("Some Error")==Left("Some Error"),"Either ApplicativeError: raiseError should lift E to Left[E,A] as invalid projection") + } + test("Either ApplicativeError: handleError should behave as identity map when acted upon a valid instance") { + val right:Either[String,Int] = Right(42) + val identical:Either[String,Int] = right.handleError[String](_=>40) + assert(identical==right,"Either ApplicativeError: handleError should behave as identity map when acted upon a valid instance") + } +// test("ValidatedNel ApplicativeError: handleError should recover an invalid operation") { +// val invalidNel:ValidatedNel[Int] = Invalid(List("ExistentialCrisisErr: Unable to find meaning of life")) +// val recover:ValidatedNel[Int] = invalidNel.handleError[NonEmptyList[String]](_=>42) +// assert(recover==Valid(42),"ValidatedNel ApplicativeError: handleError should recover an invalid operation") +// } +// test("ValidatedNel ApplicativeError: handleErrorWith should behave as identity map when acted upon a valid instance") { +// val validatedNel:ValidatedNel[Int] = Valid(42) +// val identity:ValidatedNel[Int] = validatedNel.handleErrorWith[NonEmptyList[String]](_=>Valid(40)) +// assert(identity==validatedNel,"ValidatedNel ApplicativeError: handleErrorWith should behave as identity map when acted upon a valid instance") +// } +// test("ValidatedNel ApplicativeError: handleErrorWith should recover an invalid operation") { +// val invalidNel:ValidatedNel[Int] = Invalid(List("ExistentialCrisisErr: Unable to find meaning of life")) +// val recover:ValidatedNel[Int] = invalidNel.handleErrorWith[NonEmptyList[String]](_=>Valid(42)) +// assert(recover==Valid(42),"ValidatedNel ApplicativeError: handleErrorWith should recover an invalid operation") +// } +// test("ValidatedNel Applicative: pure maps to valid") { +// assert(validatedNelInstance.pure(42)==Valid(42),"ValidatedNel Applicative: pure maps to valid") +// } +// test("ValidatedNel Apply: ap maps a valid transformation to a valid context ") { +// val validNel:ValidatedNel[Int] = Valid(42) +// val fnNel:ValidatedNel[Int=>String] = Valid({ +// case 42 => "Meaning of life" +// case _ => "Not the meaning of life" +// }) +// assert(validNel.ap(fnNel)==Valid("Meaning of life"),"ValidatedNel Apply: ap maps a valid transformation to a valid context ") +// } +// test("ValidatedNel Apply: ap maps a valid transformation to a invalid context ") { +// val invalidNel:ValidatedNel[Int] = Invalid(List("Unexpected Error")) +// val fnNel:ValidatedNel[Int=>String] = Valid({ +// case 42 => "Meaning of life" +// case _ => "Not the meaning of life" +// }) +// assert(invalidNel.ap(fnNel)==Invalid(List("Unexpected Error")),"ValidatedNel Apply: ap maps a valid transformation to a invalid context ") +// } +// test("ValidatedNel Apply: ap maps a invalid transformation to a valid context ") { +// val validNel:ValidatedNel[Int] = Valid(42) +// val invalidfnNel:ValidatedNel[Int=>String] = Invalid(List("Tranformation failure")) +// assert(validNel.ap(invalidfnNel)==Invalid(List("Tranformation failure")),"ValidatedNel Apply: ap maps a invalid transformation to a valid context") +// } +// test("ValidatedNel Apply: ap maps a invalid transformation to a invalid context (accumulate errors)") { +// val invalidNel:ValidatedNel[Int] = Invalid(List("Unexpected Error")) +// val invalidfnNel:ValidatedNel[Int=>String] = Invalid(List("Tranformation failure")) +// assert(invalidNel.ap(invalidfnNel)==Invalid(List("Tranformation failure","Unexpected Error")),"ValidatedNel Apply: ap maps a invalid transformation to a invalid context (accumulate errors)") +// } +// test("ValidatedNel Functor: map maps a valid[A] into a valid[B]") { +// val validNel:ValidatedNel[Int] = Valid(32) +// assert(validNel.map(_+10)==Valid(42),"ValidatedNel Functor: map maps a valid[A] into a valid[B]") +// } +// test("ValidatedNel Functor: map identity when instance is Invalid") { +// val invalidNel:ValidatedNel[Int] = Invalid(List("Unexpected Error")) +// assert(invalidNel.map(_+10)==invalidNel,"ValidatedNel Functor: map identity when instance is Invalid") +// } +// test("ValidatedNel Semigroupal: product maps to product category (A,B), both valid") { +// val validNel:ValidatedNel[Int] = Valid(42) +// val validNel2:ValidatedNel[Int] = Valid(42) +// assert(validNel.product(validNel2)==Valid((42,42)),"ValidatedNel Semigroupal: product A,B maps to product category (A,B), both valid") +// } +// test("ValidatedNel Semigroupal: product maps to product category (A,B), both invalid") { +// val validNel:ValidatedNel[Int] = Invalid("Error 1"::Nil) +// val validNel2:ValidatedNel[Int] = Invalid("Error 2"::Nil) +// assert(validNel.product(validNel2)==Invalid("Error 2"::"Error 1"::Nil),"ValidatedNel Semigroupal: product A,B maps to product category (A,B), both invalid") +// } +// test("ValidatedNel Semigroupal: product maps to product category (A,B), first invalid") { +// val validNel:ValidatedNel[Int] = Invalid("Error 1"::Nil) +// val validNel2:ValidatedNel[Int] = Valid(42) +// assert(validNel.product(validNel2)==Invalid("Error 1"::Nil),"ValidatedNel Semigroupal: product A,B maps to product category (A,B), first invalid") +// } +// test("ValidatedNel Semigroupal: product maps to product category (A,B), second invalid") { +// val validNel:ValidatedNel[Int] = Valid(42) +// val validNel2:ValidatedNel[Int] = Invalid("Error 2"::Nil) +// assert(validNel.product(validNel2)==Invalid("Error 2"::Nil),"ValidatedNel Semigroupal: product A,B maps to product category (A,B), second invalid") +// } +// +// //Arity (map2,product2) +// //Invariant(imap) +// +// private def expenseIdValidation(expenseID:Long):ValidatedNel[Long]= +// if(expenseID<=0) Invalid(List("Expense ID cannot be negative")) else Valid(expenseID) +// +// private def expenseAmountValidation(expenseAmount:Double):ValidatedNel[Double]= +// if(expenseAmount<=0) Invalid(List("Expense amount cannot be negative")) else Valid(expenseAmount) +// +// private def mkExpenseMapN(id:Long,amount:Double):ValidatedNel[Expense] = +// (expenseIdValidation(id), expenseAmountValidation(amount)).mapN((Expense.apply _).tupled) + +} diff --git a/~$diagram.drawio.bkp b/~$diagram.drawio.bkp new file mode 100644 index 0000000..1723428 --- /dev/null +++ b/~$diagram.drawio.bkp @@ -0,0 +1 @@ +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 \ No newline at end of file