From 25b75090541e32a5f675bfcc0225c9a63fd30e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Sowa?= Date: Wed, 16 Oct 2024 20:45:40 +0200 Subject: [PATCH 1/3] Adding missing helper methods. Improved docs. --- .../benchmarks/SealedPrograms.scala | 2 +- build.sbt | 5 +- .../sealedmonad/examples/Options.scala | 4 +- .../pl/iterators/sealedmonad/Sealed.scala | 247 +++++++++--- .../sealedmonad/syntax/SealedSyntax.scala | 372 ++++++++++++++++-- .../iterators/sealedmonad/SealedTests.scala | 3 +- .../sealedmonad/laws/SealedLaws.scala | 6 +- 7 files changed, 561 insertions(+), 78 deletions(-) diff --git a/benchmarks/src/main/scala/pl/iterators/sealedmonad/benchmarks/SealedPrograms.scala b/benchmarks/src/main/scala/pl/iterators/sealedmonad/benchmarks/SealedPrograms.scala index 12bb34a..49ab939 100644 --- a/benchmarks/src/main/scala/pl/iterators/sealedmonad/benchmarks/SealedPrograms.scala +++ b/benchmarks/src/main/scala/pl/iterators/sealedmonad/benchmarks/SealedPrograms.scala @@ -87,7 +87,7 @@ class SealedPrograms { def benchmark2 = { val s = for { m <- returnOption.valueOr(SomeCase) - _ <- doSomeOtherWork(m).valueOr(SomeOtherCase) >>! (s => M.pure(m + s.toInt)) + _ <- doSomeOtherWork(m).valueOr(SomeOtherCase).flatTap(s => M.pure(m + s.toInt)) } yield Result(m) s.run diff --git a/build.sbt b/build.sbt index af71db2..0710db7 100644 --- a/build.sbt +++ b/build.sbt @@ -144,5 +144,6 @@ lazy val sealedMonad = crossProject(JSPlatform, JVMPlatform, NativePlatform) description := "Scala library for nice for-comprehension-style error handling" ) -lazy val root = tlCrossRootProject.aggregate(sealedMonad, benchmarks, docs, examples) - .settings(baseSettings *) \ No newline at end of file +lazy val root = tlCrossRootProject + .aggregate(sealedMonad, benchmarks, docs, examples) + .settings(baseSettings *) diff --git a/examples/src/main/scala/pl/iterators/sealedmonad/examples/Options.scala b/examples/src/main/scala/pl/iterators/sealedmonad/examples/Options.scala index a617e9e..1439c33 100644 --- a/examples/src/main/scala/pl/iterators/sealedmonad/examples/Options.scala +++ b/examples/src/main/scala/pl/iterators/sealedmonad/examples/Options.scala @@ -93,7 +93,9 @@ object Options { ): M[ConfirmResponse] = { val s = for { method <- findAuthMethod(token).valueOr[ConfirmResponse](ConfirmResponse.MethodNotFound) - user <- findUser(method.userId).valueOr[ConfirmResponse](ConfirmResponse.UserNotFound) ! upsertAuthMethod(confirmMethod(method)) + user <- findUser(method.userId) + .valueOr[ConfirmResponse](ConfirmResponse.UserNotFound) + .flatTap(_ => upsertAuthMethod(confirmMethod(method))) } yield ConfirmResponse.Confirmed(issueTokenFor(user)) s.run diff --git a/sealedmonad/src/main/scala/pl/iterators/sealedmonad/Sealed.scala b/sealedmonad/src/main/scala/pl/iterators/sealedmonad/Sealed.scala index 933783b..a15eff6 100644 --- a/sealedmonad/src/main/scala/pl/iterators/sealedmonad/Sealed.scala +++ b/sealedmonad/src/main/scala/pl/iterators/sealedmonad/Sealed.scala @@ -7,13 +7,19 @@ import scala.Function.const sealed trait Sealed[F[_], +A, +ADT] { import Sealed.* + + /** Transforms intermediate value `A` to `B` using a pure function. + */ def map[B](f: A => B): Sealed[F, B, ADT] = Transform(this, f.andThen(left[F, B, ADT]), right[F, B, ADT]) + + /** Transforms intermediate value `A` to `B` using a function that returns a Sealed instance. + */ def flatMap[B, ADT1 >: ADT](f: A => Sealed[F, B, ADT1]): Sealed[F, B, ADT1] = Transform(this, f, right[F, B, ADT1]) /** Alias for map(_ => ()) */ final def void: Sealed[F, Unit, ADT] = map(_ => ()) - /** Transforms `A` to `B` using an effectful function. + /** Transforms intermediate value `A` to `B` using an effectful function. * * Example: * {{{ @@ -30,6 +36,22 @@ sealed trait Sealed[F[_], +A, +ADT] { */ final def semiflatMap[B](f: A => F[B]): Sealed[F, B, ADT] = Transform(this, f.andThen(leftF), right[F, B, ADT]) + /** Transforms final value `ADT` to `ADT1` using an effectful function. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.* + * scala> import cats.Id + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> case class Transformed(i: Int) extends Response + * scala> val sealedSome: Sealed[Id, Int, Response] = Id(None: Option[Int]).valueOr(NotFound) + * scala> (for { x <- sealedSome.leftSemiflatMap(_ => Id(Transformed(2))) } yield Value(x)).run + * res0: cats.Id[Response] = Transformed(2) + * }}} + */ final def leftSemiflatMap[ADT1 >: ADT](f: ADT => F[ADT1]): Sealed[F, A, ADT1] = Transform(this, left[F, A, ADT1], f.andThen(rightF)) @@ -109,7 +131,7 @@ sealed trait Sealed[F[_], +A, +ADT] { */ final def complete[ADT1 >: ADT](f: A => ADT1): Sealed[F, Nothing, ADT1] = flatMap(a => right(f(a))) - /** Effectful version of `complete`. + /** Finishes the computation by returning Sealed with given `F[ADT]`. * * Example: * {{{ @@ -127,13 +149,13 @@ sealed trait Sealed[F[_], +A, +ADT] { */ final def completeWith[ADT1 >: ADT](f: A => F[ADT1]): Sealed[F, Nothing, ADT1] = flatMap(f.andThen(rightF)) - /** Converts `Sealed[F, Either[ADT1, B], ADT]` into `Sealed[F, B, ADT1]`. Usually paired with `either`. See `Sealed#either` for example + /** Maps `Sealed[F, Either[ADT1, B], ADT]` into `Sealed[F, B, ADT1]`. Usually paired with `either`. See `Sealed#either` for example * usage. */ final def rethrow[B, ADT1 >: ADT](implicit ev: A <:< Either[ADT1, B]): Sealed[F, B, ADT1] = flatMap(a => ev(a).fold(right, left)) - /** Converts `A` into `Either[ADT1, B]` and creates a Sealed instance from the result. + /** Maps `A` into `Either[ADT1, B]` and creates a Sealed instance from the result. * * Example: * {{{ @@ -156,7 +178,7 @@ sealed trait Sealed[F[_], +A, +ADT] { */ final def attempt[B, ADT1 >: ADT](f: A => Either[ADT1, B]): Sealed[F, B, ADT1] = map(f).rethrow - /** Effectful version of `attempt`. + /** Converts `A` into `F[Either[ADT1, B]]` and creates a Sealed instance from the result. * * Example: * {{{ @@ -271,8 +293,7 @@ sealed trait Sealed[F[_], +A, +ADT] { final def inspectF(pf: PartialFunction[Either[ADT, A], F[Any]]): Sealed[F, A, ADT] = either.flatTapWhen(e => pf.isDefinedAt(e), pf).rethrow - /** Variation of `ensure` that allows you to access `A` in `orElse` parameter. Returns unchanged Sealed instance if condition is met, - * otherwise ends execution with specified `ADT`. + /** Returns unchanged Sealed instance if condition is met, otherwise ends execution with specified `ADT`. * * Example: * {{{ @@ -282,16 +303,15 @@ sealed trait Sealed[F[_], +A, +ADT] { * scala> sealed trait Response * scala> case class Value(i: Int) extends Response * scala> case object NotFound extends Response - * scala> case class UnwantedNumber(i: Int) extends Response + * scala> case object ConditionNotMet extends Response * scala> val sealedSome: Sealed[Id, Int, Response] = Id(Option(1)).valueOr(NotFound) - * scala> (for { x <- sealedSome.ensureOr(num => num == 2, num => UnwantedNumber(num)) } yield Value(x)).run - * res0: cats.Id[Response] = UnwantedNumber(1) + * scala> (for { x <- sealedSome.ensure(num => num == 1, ConditionNotMet) } yield Value(x)).run + * res0: cats.Id[Response] = Value(1) * }}} */ - final def ensureOr[ADT1 >: ADT](pred: A => Boolean, orElse: A => ADT1): Sealed[F, A, ADT1] = - attempt(a => Either.cond(pred(a), a, orElse(a))) + final def ensure[ADT1 >: ADT](pred: A => Boolean, orElse: => ADT1): Sealed[F, A, ADT1] = ensureOr(pred, _ => orElse) - /** Returns unchanged Sealed instance if condition is met, otherwise ends execution with specified `ADT`. + /** Returns unchanged Sealed instance if condition is met, otherwise ends execution with specified `F[ADT]`. * * Example: * {{{ @@ -301,13 +321,19 @@ sealed trait Sealed[F[_], +A, +ADT] { * scala> sealed trait Response * scala> case class Value(i: Int) extends Response * scala> case object NotFound extends Response - * scala> case object ConditionNotMet extends Response + * scala> case class Transformed(i: Int) extends Response * scala> val sealedSome: Sealed[Id, Int, Response] = Id(Option(1)).valueOr(NotFound) - * scala> (for { x <- sealedSome.ensure(num => num == 1, ConditionNotMet) } yield Value(x)).run + * scala> (for { x <- sealedSome.ensureF(num => num == 1, Id(Transformed(2))) } yield Value(x)).run * res0: cats.Id[Response] = Value(1) + * scala> (for { x <- sealedSome.ensureF(num => num == 2, Id(Transformed(2))) } yield Value(x)).run + * res1: cats.Id[Response] = Transformed(2) + * scala> val sealedNone: Sealed[Id, Int, Response] = Id(Option.empty).valueOr(NotFound) + * scala> (for { x <- sealedNone.ensureF(num => num == 1, Id(Transformed(2))) } yield Value(x)).run + * res2: cats.Id[Response] = NotFound * }}} */ - final def ensure[ADT1 >: ADT](pred: A => Boolean, orElse: => ADT1): Sealed[F, A, ADT1] = ensureOr(pred, _ => orElse) + final def ensureF[ADT1 >: ADT](pred: A => Boolean, orElse: => F[ADT1]): Sealed[F, A, ADT1] = + ensureOrF(pred, _ => orElse) /** Returns unchanged Sealed instance if condition is not met, otherwise ends execution with specified `ADT`. * @@ -327,7 +353,51 @@ sealed trait Sealed[F[_], +A, +ADT] { */ final def ensureNot[ADT1 >: ADT](pred: A => Boolean, orElse: => ADT1): Sealed[F, A, ADT1] = ensure(a => !pred(a), orElse) - /** Effectful version of `ensureOr`. + /** Returns unchanged Sealed instance if condition is not met, otherwise ends execution with specified `F[ADT]`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.* + * scala> import cats.Id + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> case class Transformed(i: Int) extends Response + * scala> val sealedSome: Sealed[Id, Int, Response] = Id(Option(1)).valueOr(NotFound) + * scala> (for { x <- sealedSome.ensureNotF(num => num == 1, Id(Transformed(2))) } yield Value(x)).run + * res0: cats.Id[Response] = Transformed(2) + * scala> (for { x <- sealedSome.ensureNotF(num => num == 2, Id(Transformed(2))) } yield Value(x)).run + * res1: cats.Id[Response] = Value(1) + * scala> val sealedNone: Sealed[Id, Int, Response] = Id(Option.empty).valueOr(NotFound) + * scala> (for { x <- sealedNone.ensureNotF(num => num == 1, Id(Transformed(2))) } yield Value(x)).run + * res2: cats.Id[Response] = NotFound + * }}} + */ + final def ensureNotF[ADT1 >: ADT](pred: A => Boolean, orElse: => F[ADT1]): Sealed[F, A, ADT1] = ensureF(a => !pred(a), orElse) + + /** Variation of `ensure` that allows you to access `A` in `orElse` parameter. Returns unchanged Sealed instance if condition is met, + * otherwise ends execution with specified `ADT` produced from `A`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.* + * scala> import cats.Id + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> case class UnwantedNumber(i: Int) extends Response + * scala> val sealedSome: Sealed[Id, Int, Response] = Id(Option(1)).valueOr(NotFound) + * scala> (for { x <- sealedSome.ensureOr(num => num == 2, num => UnwantedNumber(num)) } yield Value(x)).run + * res0: cats.Id[Response] = UnwantedNumber(1) + * }}} + */ + final def ensureOr[ADT1 >: ADT](pred: A => Boolean, orElse: A => ADT1): Sealed[F, A, ADT1] = + attempt(a => Either.cond(pred(a), a, orElse(a))) + + /** Variation of `ensure` that allows you to access `A` in `orElse` parameter. Returns unchanged Sealed instance if condition is met, + * otherwise ends execution with specified `F[ADT]` produced from `A`. * * Example: * {{{ @@ -351,7 +421,27 @@ sealed trait Sealed[F[_], +A, +ADT] { final def ensureOrF[ADT1 >: ADT](pred: A => Boolean, orElse: A => F[ADT1]): Sealed[F, A, ADT1] = flatMap(a => if (pred(a)) left(a) else completeWith(orElse)) - /** Effectful version of `ensure`. + /** Variation of `ensure` that allows you to access `A` in `orElse` parameter. Returns unchanged Sealed instance if condition is not met, + * otherwise ends execution with specified `ADT`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.* + * scala> import cats.Id + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> case class UnwantedNumber(i: Int) extends Response + * scala> val sealedSome: Sealed[Id, Int, Response] = Id(Option(1)).valueOr(NotFound) + * scala> (for { x <- sealedSome.ensureNotOr(num => num == 1, num => UnwantedNumber(num)) } yield Value(x)).run + * res0: cats.Id[Response] = UnwantedNumber(1) + * }}} + */ + final def ensureNotOr[ADT1 >: ADT](pred: A => Boolean, orElse: A => ADT1): Sealed[F, A, ADT1] = ensureOr(a => !pred(a), orElse) + + /** Variation of `ensure` that allows you to access `A` in `orElse` parameter. Returns unchanged Sealed instance if condition is not met, + * otherwise ends execution with specified `F[ADT]`. * * Example: * {{{ @@ -363,17 +453,16 @@ sealed trait Sealed[F[_], +A, +ADT] { * scala> case object NotFound extends Response * scala> case class Transformed(i: Int) extends Response * scala> val sealedSome: Sealed[Id, Int, Response] = Id(Option(1)).valueOr(NotFound) - * scala> (for { x <- sealedSome.ensureF(num => num == 1, Id(Transformed(2))) } yield Value(x)).run - * res0: cats.Id[Response] = Value(1) - * scala> (for { x <- sealedSome.ensureF(num => num == 2, Id(Transformed(2))) } yield Value(x)).run - * res1: cats.Id[Response] = Transformed(2) + * scala> (for { x <- sealedSome.ensureNotOrF(num => num == 1, _ => Id(Transformed(2))) } yield Value(x)).run + * res0: cats.Id[Response] = Transformed(2) + * scala> (for { x <- sealedSome.ensureNotOrF(num => num == 2, _ => Id(Transformed(2))) } yield Value(x)).run + * res1: cats.Id[Response] = Value(1) * scala> val sealedNone: Sealed[Id, Int, Response] = Id(Option.empty).valueOr(NotFound) - * scala> (for { x <- sealedNone.ensureF(num => num == 1, Id(Transformed(2))) } yield Value(x)).run + * scala> (for { x <- sealedNone.ensureNotOrF(num => num == 1, _ => Id(Transformed(2))) } yield Value(x)).run * res2: cats.Id[Response] = NotFound * }}} */ - final def ensureF[ADT1 >: ADT](pred: A => Boolean, orElse: => F[ADT1]): Sealed[F, A, ADT1] = - ensureOrF(pred, _ => orElse) + final def ensureNotOrF[ADT1 >: ADT](pred: A => Boolean, orElse: A => F[ADT1]): Sealed[F, A, ADT1] = ensureOrF(a => !pred(a), orElse) /** Executes a side effect on value `A` if present, and returns unchanged `Sealed[F, A, ADT]`. * @@ -445,6 +534,46 @@ sealed trait Sealed[F[_], +A, +ADT] { final def flatTapWhen[B](cond: A => Boolean, f: A => F[B]): Sealed[F, A, ADT] = flatMap(a => if (cond(a)) flatTap(f) else left(a)) + /** Executes a side effect on value `A` if present and given condition is not met. Returns unchanged `Sealed[F, A, ADT]`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.* + * scala> import cats.Id + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[Id, Int, Response] = Id(Option(1)).valueOr(NotFound) + * scala> (for { x <- sealedSome.flatTapWhenNot(num => num == 2, _ => Id(println("right"))) } yield Value(x)).run + * res0: cats.Id[Response] = Value(1) + * // prints 'right' + * scala> (for { x <- sealedSome.flatTapWhenNot(num => num == 1, _ => Id(println("left"))) } yield Value(x)).run + * res1: cats.Id[Response] = Value(1) + * // doesn't print anything + * scala> val sealedNone: Sealed[Id, Int, Response] = Id(Option.empty).valueOr(NotFound) + * scala> (for { x <- sealedNone.flatTapWhenNot(num => num == 2, _ => Id(println("left"))) } yield Value(x)).run + * res1: cats.Id[Response] = NotFound + * // doesn't print anything + * }}} + */ + final def flatTapWhenNot[B](cond: A => Boolean, f: A => F[B]): Sealed[F, A, ADT] = flatTapWhen(a => !cond(a), f) + + /** Evaluates Sealed to `F[ADT]` given that `A` (intermediate) is a subtype of `ADT` (final value). + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.* + * scala> import cats.Id + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[Id, Response, Response] = Id(Option(1)).valueOr(NotFound).map(Value(_)) + * scala> sealedSome.run + * res0: cats.Id[Response] = Value(1) + * }}} + */ final def run[ADT1 >: ADT](implicit ev: A <:< ADT1, F: Monad[F]): F[ADT1] = eval(this).map(_.fold(ev, identity)) } @@ -452,42 +581,69 @@ object Sealed extends SealedInstances { import cats.syntax.either.* + /** Creates a Sealed instance from a value of type `F[A]` as intermediate value. + */ def apply[F[_], A](value: => F[A]): Sealed[F, A, Nothing] = defer(leftF(value)) - def liftF[F[_], A](value: A): Sealed[F, A, Nothing] = defer(left(value)) - def seal[F[_], A](value: A): Sealed[F, Nothing, A] = defer(right(value)) + /** Creates a Sealed instance from a value of type `A` as intermediate value. + */ + def liftF[F[_], A](value: A): Sealed[F, A, Nothing] = defer(left(value)) + + /** Creates a Sealed instance from a value of type `ADT` as final value. + */ + def seal[F[_], ADT](value: ADT): Sealed[F, Nothing, ADT] = defer(right(value)) + /** Creates a Sealed instance from a value of type `F[ADT]` as final value. + */ def result[F[_], ADT](value: => F[ADT]): Sealed[F, Nothing, ADT] = defer(rightF(value)) + /** Creates a Sealed value from `F[Option[A]]` with A as intermediate value if present, or `ADT` as final value if not. + */ def valueOr[F[_], A, ADT](fa: => F[Option[A]], orElse: => ADT): Sealed[F, A, ADT] = apply(fa).flatMap { case Some(a) => left(a) case None => right(orElse) } + /** Creates a Sealed instance from `F[Option[A]]` with A as intermediate value if present, or `F[ADT]` as final value if not. + */ + def valueOrF[F[_], A, ADT](fa: => F[Option[A]], orElse: => F[ADT]): Sealed[F, A, ADT] = + apply(fa).flatMap { + case Some(a) => left(a) + case None => rightF(orElse) + } + + /** Creates a Sealed instance from `F[Option[A]]` with Unit as intermediate value if not present, or `ADT` based on `A` as final value + * otherwise. + */ + def emptyOr[F[_], A, ADT](fa: => F[Option[A]], orElse: A => ADT): Sealed[F, Unit, ADT] = apply(fa).flatMap { + case Some(a) => right(orElse(a)) + case None => left(()) + } + + /** Creates a Sealed instance from `F[Option[A]]` with Unit as intermediate value if not present, or `F[ADT]` based on `A` as final value + * otherwise. + */ + def emptyOrF[F[_], A, ADT](fa: => F[Option[A]], orElse: A => F[ADT]): Sealed[F, Unit, ADT] = apply(fa).flatMap { + case Some(a) => rightF(orElse(a)) + case None => left(()) + } + /** Shorthand for F.pure(()).seal * * Example: * {{{ * scala> import pl.iterators.sealedmonad.Sealed * scala> import pl.iterators.sealedmonad.syntax.* - * scala> import cats.Id - * scala> Sealed.unit[Id] == Id.pure(()).seal - * res0: true + * scala> Sealed.unit[Option].run + * res0: Option[Unit] = Some(()) * }}} */ def unit[F[_]]: Sealed[F, Unit, Nothing] = liftF(()) - def valueOrF[F[_], A, ADT](fa: => F[Option[A]], orElse: => F[ADT]): Sealed[F, A, ADT] = - apply(fa).flatMap { - case Some(a) => left(a) - case None => rightF(orElse) - } - + /** Transforms `F[Either[A, B]]` into `Sealed[F, B, ADT]` by transforming `A` into `ADT` using `f`. + */ def handleError[F[_], A, B, ADT](fa: F[Either[A, B]])(f: A => ADT): Sealed[F, B, ADT] = apply(fa).attempt(_.leftMap(f)) - def bimap[F[_], A, B, C, ADT](fa: F[Either[A, B]])(f: A => ADT)(fb: B => C): Sealed[F, C, ADT] = - apply(fa).attempt(_.leftMap(f).map(fb)) - /** Represents either an intermediate A or a final ADT. */ private final case class Pure[F[_], A, ADT]( @@ -547,20 +703,21 @@ object Sealed extends SealedInstances { case Suspend(Right(fadt)) => fadt.flatMap(adt => recur(Transform(Pure(Right(adt)), onA, onADT))) case Defer(value) => recur(Transform(value(), onA, onADT)) case Transform(next, onA0, onADT0) => + // the asInstanceOf below are for cross Scala 2/3 compatibility and can be avoided when src code would be split recur( Transform[F, Any, A, Any, ADT]( next, (a0: Any) => Transform[F, Any, A, Any, ADT]( - defer(onA0(a0)), - onA, - onADT + defer(onA0.asInstanceOf[Any => Sealed[F, A, ADT]](a0)), + onA.asInstanceOf[Any => Sealed[F, A, ADT]], + onADT.asInstanceOf[Any => Sealed[F, A, ADT]] ), (adt0: Any) => Transform[F, Any, A, Any, ADT]( - defer(onADT0(adt0)), - onA, - onADT + defer(onADT0.asInstanceOf[Any => Sealed[F, Any, Any]](adt0)), + onA.asInstanceOf[Any => Sealed[F, A, ADT]], + onADT.asInstanceOf[Any => Sealed[F, A, ADT]] ) ) ) diff --git a/sealedmonad/src/main/scala/pl/iterators/sealedmonad/syntax/SealedSyntax.scala b/sealedmonad/src/main/scala/pl/iterators/sealedmonad/syntax/SealedSyntax.scala index 4e07361..39b7ae9 100644 --- a/sealedmonad/src/main/scala/pl/iterators/sealedmonad/syntax/SealedSyntax.scala +++ b/sealedmonad/src/main/scala/pl/iterators/sealedmonad/syntax/SealedSyntax.scala @@ -2,26 +2,232 @@ package pl.iterators.sealedmonad.syntax import pl.iterators.sealedmonad.Sealed -import scala.language.{higherKinds, implicitConversions} +import scala.language.implicitConversions trait SealedSyntax { implicit final def faSyntax[F[_], A](fa: F[A]): SealedFAOps[F, A] = new SealedFAOps(fa) implicit final def faOps[A](a: A): SealedOps[A] = new SealedOps(a) implicit final def faOptSyntax[F[_], A](fa: F[Option[A]]): SealedFOptAOps[F, A] = new SealedFOptAOps(fa) implicit final def faEitherSyntax[F[_], A, B](fa: F[Either[A, B]]): SealedFAEitherOps[F, A, B] = new SealedFAEitherOps(fa) - implicit final def sealedSyntax[F[_], A, ADT](s: Sealed[F, A, ADT]): SealedSymbolic[F, A, ADT] = new SealedSymbolic(s) - implicit final def optSyntax[A](condOpt: Option[A]): SealedOptOps[A] = new SealedOptOps[A](condOpt) implicit final def eitherSyntax[ADT, A](condEither: Either[ADT, A]): SealedEitherOps[ADT, A] = new SealedEitherOps[ADT, A](condEither) } final class SealedFAOps[F[_], A](private val self: F[A]) extends AnyVal { - def seal[ADT]: Sealed[F, A, ADT] = Sealed(self) - def ensure[ADT](pred: A => Boolean, orElse: => ADT): Sealed[F, A, ADT] = seal[ADT].ensure(pred, orElse) - def ensureOr[ADT](pred: A => Boolean, orElse: A => ADT): Sealed[F, A, ADT] = seal[ADT].ensureOr(pred, orElse) - def ensureF[ADT](pred: A => Boolean, orElse: => F[ADT]): Sealed[F, A, ADT] = seal[ADT].ensureF(pred, orElse) + + /** Creates a Sealed instance from a value of type `F[A]` as intermediate value. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Int, Response] = List(1).seal[Response] + * scala> (for {value <- sealedSome} yield Value(value)).run + * res0: List[Response] = List(Value(1)) + * }}} + */ + def seal[ADT]: Sealed[F, A, ADT] = Sealed(self) + + /** Creates Sealed instance from a value of type `F[A]` as intermediate value if condition is met, otherwise ends execution with + * specified `ADT`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Int, Response] = List(1).ensure(_ == 1, NotFound) + * scala> (for {value <- sealedSome} yield Value(value)).run + * res0: List[Response] = List(Value(1)) + * scala> val sealedNone: Sealed[List, Int, Response] = List(2).ensure(_ == 1, NotFound) + * scala> (for {value <- sealedNone} yield Value(value)).run + * res1: List[Response] = List(NotFound) + * }}} + */ + def ensure[ADT](pred: A => Boolean, orElse: => ADT): Sealed[F, A, ADT] = seal[ADT].ensure(pred, orElse) + + /** Creates Sealed instance from a value of type `F[A]` as intermediate value if condition is met, otherwise ends execution with + * specified F[`ADT`]. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Int, Response] = List(1).ensureF(_ == 1, List(NotFound)) + * scala> (for {value <- sealedSome} yield Value(value)).run + * res0: List[Response] = List(Value(1)) + * scala> val sealedNone: Sealed[List, Int, Response] = List(2).ensureF(_ == 1, List(NotFound)) + * scala> (for {value <- sealedNone} yield Value(value)).run + * res1: List[Response] = List(NotFound) + * }}} + */ + def ensureF[ADT](pred: A => Boolean, orElse: => F[ADT]): Sealed[F, A, ADT] = seal[ADT].ensureF(pred, orElse) + + /** Creates Sealed instance from a value of type `F[A]` as intermediate value if condition is not met, otherwise ends execution with + * specified `ADT`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Int, Response] = List(1).ensureNot(_ == 2, NotFound) + * scala> (for {value <- sealedSome} yield Value(value)).run + * res0: List[Response] = List(Value(1)) + * scala> val sealedNone: Sealed[List, Int, Response] = List(2).ensureNot(_ == 2, NotFound) + * scala> (for {value <- sealedNone} yield Value(value)).run + * res1: List[Response] = List(NotFound) + * }}} + */ + def ensureNot[ADT](pred: A => Boolean, orElse: => ADT): Sealed[F, A, ADT] = seal[ADT].ensureNot(pred, orElse) + + /** Creates Sealed instance from a value of type `F[A]` as intermediate value if condition is not met, otherwise ends execution with + * specified F[`ADT`]. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Int, Response] = List(1).ensureNotF(_ == 2, List(NotFound)) + * scala> (for {value <- sealedSome} yield Value(value)).run + * res0: List[Response] = List(Value(1)) + * scala> val sealedNone: Sealed[List, Int, Response] = List(2).ensureNotF(_ == 2, List(NotFound)) + * scala> (for {value <- sealedNone} yield Value(value)).run + * res1: List[Response] = List(NotFound) + * }}} + */ + def ensureNotF[ADT](pred: A => Boolean, orElse: => F[ADT]): Sealed[F, A, ADT] = seal[ADT].ensureNotF(pred, orElse) + + /** Creates Sealed instance from a value of type `F[A]` as intermediate value if condition is met, otherwise ends execution with + * specified `ADT` produced from `A`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Int, Response] = List(1).ensureOr(_ == 1, x => Value(x)) + * scala> (for {value <- sealedSome} yield NotFound).run + * res0: List[Response] = List(NotFound) + * scala> val sealedNone: Sealed[List, Int, Response] = List(2).ensureOr(_ == 1, x => Value(x)) + * scala> (for {value <- sealedNone} yield NotFound).run + * res1: List[Response] = List(Value(2)) + * }}} + */ + def ensureOr[ADT](pred: A => Boolean, orElse: A => ADT): Sealed[F, A, ADT] = seal[ADT].ensureOr(pred, orElse) + + /** Creates Sealed instance from a value of type `F[A]` as intermediate value if condition is met, otherwise ends execution with + * specified F[`ADT`] produced from `A`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Int, Response] = List(1).ensureOrF(_ == 1, x => List(Value(x))) + * scala> (for {value <- sealedSome} yield NotFound).run + * res0: List[Response] = List(NotFound) + * scala> val sealedNone: Sealed[List, Int, Response] = List(2).ensureOrF(_ == 1, x => List(Value(x))) + * scala> (for {value <- sealedNone} yield NotFound).run + * res1: List[Response] = List(Value(2)) + * }}} + */ def ensureOrF[ADT](pred: A => Boolean, orElse: A => F[ADT]): Sealed[F, A, ADT] = seal[ADT].ensureOrF(pred, orElse) - def attempt[B, ADT](f: A => Either[ADT, B]): Sealed[F, B, ADT] = seal[ADT].attempt(f) - def attemptF[B, ADT](f: A => F[Either[ADT, B]]): Sealed[F, B, ADT] = seal[ADT].attemptF(f) + + /** Creates Sealed instance from a value of type `F[A]` as intermediate value if condition is not met, otherwise ends execution with + * specified `ADT` produced from `A`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Int, Response] = List(1).ensureNotOr(_ == 2, x => Value(x)) + * scala> (for {value <- sealedSome} yield NotFound).run + * res0: List[Response] = List(NotFound) + * scala> val sealedNone: Sealed[List, Int, Response] = List(2).ensureNotOr(_ == 2, x => Value(x)) + * scala> (for {value <- sealedNone} yield NotFound).run + * res1: List[Response] = List(Value(2)) + * }}} + */ + def ensureNotOr[ADT](pred: A => Boolean, orElse: A => ADT): Sealed[F, A, ADT] = seal[ADT].ensureNotOr(pred, orElse) + + /** Creates Sealed instance from a value of type `F[A]` as intermediate value if condition is not met, otherwise ends execution with + * specified F[`ADT`] produced from `A`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Int, Response] = List(1).ensureNotOrF(_ == 2, x => List(Value(x))) + * scala> (for {value <- sealedSome} yield NotFound).run + * res0: List[Response] = List(NotFound) + * scala> val sealedNone: Sealed[List, Int, Response] = List(2).ensureNotOrF(_ == 2, x => List(Value(x))) + * scala> (for {value <- sealedNone} yield NotFound).run + * res1: List[Response] = List(Value(2)) + * }}} + */ + def ensureNotOrF[ADT](pred: A => Boolean, orElse: A => F[ADT]): Sealed[F, A, ADT] = + seal[ADT].ensureNotOrF(pred, orElse) + + /** Converts `A` into `Either[ADT1, B]` and creates a Sealed instance from the result. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Int, Response] = List(1).attempt { case i if i == 1 => Right(i); case _ => Left(NotFound) } + * scala> (for {value <- sealedSome} yield Value(value)).run + * res0: List[Response] = List(Value(1)) + * scala> val sealedNone: Sealed[List, Int, Response] = List(2).attempt { case i if i == 1 => Right(i); case _ => Left(NotFound) } + * scala> (for {value <- sealedNone} yield Value(value)).run + * res1: List[Response] = List(NotFound) + * }}} + */ + def attempt[B, ADT](f: A => Either[ADT, B]): Sealed[F, B, ADT] = seal[ADT].attempt(f) + + /** Converts `A` into `F[Either[ADT1, B]]` and creates a Sealed instance from the result. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Int, Response] = List(1).attemptF(i => List(Right(i))) + * scala> (for {value <- sealedSome} yield Value(value)).run + * res0: List[Response] = List(Value(1)) + * scala> val sealedNone: Sealed[List, Int, Response] = List(2).attemptF(i => List(Left(NotFound))) + * scala> (for {value <- sealedNone} yield Value(value)).run + * res1: List[Response] = List(NotFound) + * }}} + */ + def attemptF[B, ADT](f: A => F[Either[ADT, B]]): Sealed[F, B, ADT] = seal[ADT].attemptF(f) } final class SealedFOptAOps[F[_], A](private val self: F[Option[A]]) extends AnyVal { @@ -63,12 +269,102 @@ final class SealedFOptAOps[F[_], A](private val self: F[Option[A]]) extends AnyV * }}} */ def valueOrF[ADT](orElse: => F[ADT]): Sealed[F, A, ADT] = Sealed.valueOrF(self, orElse) + + /** Creates a Sealed instance from `F[Option[A]]` with Unit as intermediate value if not present, or `ADT` based on `A` as final value + * otherwise. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFOptAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Unit, Response] = List(Option(1)).emptyOr(Value(_)) + * scala> (for {value <- sealedSome} yield NotFound).run + * res0: List[Response] = List(Value(1)) + * scala> val sealedNone: Sealed[List, Unit, Response] = List(Option.empty[Int]).emptyOr(Value(_)) + * scala> (for {value <- sealedNone} yield NotFound).run + * res1: List[Response] = List(NotFound) + * }}} + */ + def emptyOr[ADT](orElse: A => ADT): Sealed[F, Unit, ADT] = Sealed.emptyOr(self, orElse) + + /** Creates a Sealed instance from `F[Option[A]]` with Unit as intermediate value if not present, or `F[ADT]` based on `A` as final value + * otherwise. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFOptAOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[List, Unit, Response] = List(Option(1)).emptyOrF(i => List(Value(i))) + * scala> (for {value <- sealedSome} yield NotFound).run + * res0: List[Response] = List(Value(1)) + * scala> val sealedNone: Sealed[List, Unit, Response] = List(Option.empty[Int]).emptyOrF(i => List(Value(i))) + * scala> (for {value <- sealedNone} yield NotFound).run + * res1: List[Response] = List(NotFound) + * }}} + */ + def emptyOrF[ADT](orElse: A => F[ADT]): Sealed[F, Unit, ADT] = Sealed.emptyOrF(self, orElse) } final class SealedFAEitherOps[F[_], A, B](private val self: F[Either[A, B]]) extends AnyVal { - def merge[ADT](f: Either[A, B] => ADT): Sealed[F, ADT, ADT] = Sealed(self).complete(f) + + /** Transforms `F[Either[A, B]]` into `Sealed[F, ADT, ADT]` using `f`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAEitherOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> List(Right(1): Either[String, Int]).merge[Response] { case Right(i) => Value(i); case Left(_) => NotFound }.run + * res0: List[Response] = List(Value(1)) + * scala> List(Left("it"): Either[String, Int]).merge[Response] { case Right(i) => Value(i); case Left(_) => NotFound }.run + * res1: List[Response] = List(NotFound) + * }}} + */ + def merge[ADT](f: Either[A, B] => ADT): Sealed[F, ADT, ADT] = Sealed(self).complete(f) + + /** Transforms `F[Either[A, B]]` into `Sealed[F, ADT, ADT]` using effectful `f`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAEitherOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> List(Right(1): Either[String, Int]).mergeF[Response] { case Right(i) => List(Value(i)); case Left(_) => List(NotFound) }.run + * res0: List[Response] = List(Value(1)) + * scala> List(Left("it"): Either[String, Int]).mergeF[Response] { case Right(i) => List(Value(i)); case Left(_) => List(NotFound) }.run + * res1: List[Response] = List(NotFound) + * }}} + */ def mergeF[ADT](f: Either[A, B] => F[ADT]): Sealed[F, ADT, ADT] = Sealed(self).completeWith(f) - def handleError[ADT](f: A => ADT): Sealed[F, B, ADT] = Sealed.handleError(self)(f) + + /** Transforms `F[Either[A, B]]` into `Sealed[F, B, ADT]` by transforming `A` into `ADT` using `f`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedFAEitherOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedRight: Sealed[List, Int, Response] = List(Right(1): Either[String, Int]).handleError(_ => NotFound) + * scala> (for {value <- sealedRight} yield Value(value)).run + * res0: List[Response] = List(Value(1)) + * scala> val sealedLeft: Sealed[List, String, Response] = List(Left(1): Either[Int, String]).handleError(Value(_)) + * scala> (for {value <- sealedLeft} yield NotFound).run + * res1: List[Response] = List(Value(1)) + * }}} + */ + def handleError[ADT](f: A => ADT): Sealed[F, B, ADT] = Sealed.handleError(self)(f) /** Returns a Sealed instance containing value `A` if it is Left in Either, else returns a Sealed instance containing value `B` if it is * Right in Either. @@ -94,23 +390,49 @@ final class SealedFAEitherOps[F[_], A, B](private val self: F[Either[A, B]]) ext } final class SealedOps[A](private val self: A) extends AnyVal { - def seal[F[_]]: Sealed[F, Nothing, A] = Sealed.seal(self) - def liftSealed[F[_], ADT]: Sealed[F, A, ADT] = Sealed.liftF(self) -} -final class SealedOptOps[A](private val self: Option[A]) extends AnyVal { - def sealCond[F[_], ADT] = new SealOptCondPartiallyApplied(self) -} + /** Creates a Sealed instance containing intermediate value `A`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.syntax.SealedOps + * scala> import cats.Id + * scala> 1.seal[Id].run + * res0: Int = 1 + * }}} + */ + def seal[F[_]]: Sealed[F, Nothing, A] = Sealed.seal(self) -final class SealedEitherOps[ADT, A](private val self: Either[ADT, A]) extends AnyVal { - def rethrow[F[_]]: Sealed[F, A, ADT] = Sealed.liftF(self).rethrow + /** Creates a Sealed instance containing intermediate value `A` and lifts it to a given `ADT`. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.syntax.SealedOps + * scala> import cats.Id + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> 1.liftSealed[Id, Response].map(Value(_)).run + * res0: Response = Value(1) + * }}} + */ + def liftSealed[F[_], ADT]: Sealed[F, A, ADT] = Sealed.liftF(self) } -final class SealOptCondPartiallyApplied[F[_], A](private val self: Option[A]) { - def apply[ADT](ifNone: => ADT): Sealed[F, A, ADT] = self.fold[Sealed[F, A, ADT]](Sealed.seal(ifNone))(a => Sealed.liftF(a)) -} +final class SealedEitherOps[ADT, A](private val self: Either[ADT, A]) extends AnyVal { -final class SealedSymbolic[F[_], A, ADT](private val self: Sealed[F, A, ADT]) extends AnyVal { - def >>![B](f: A => F[B]): Sealed[F, A, ADT] = self.flatTap(f) - def ![B](sideEffect: F[B]): Sealed[F, A, ADT] = >>!(_ => sideEffect) + /** Creates a Sealed instance containing intermediate value `A` if it is Right in Either, else short-circuits with given `ADT` value. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax.SealedEitherOps + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedRight: Sealed[List, Int, Response] = (Right(1): Either[Response, Int]).rethrow[List] + * scala> (for {value <- sealedRight} yield Value(value)).run + * res0: List[Response] = List(Value(1)) + * }}} + */ + def rethrow[F[_]]: Sealed[F, A, ADT] = Sealed.liftF(self).rethrow } diff --git a/sealedmonad/src/test/scala/pl/iterators/sealedmonad/SealedTests.scala b/sealedmonad/src/test/scala/pl/iterators/sealedmonad/SealedTests.scala index 25ce883..19be084 100644 --- a/sealedmonad/src/test/scala/pl/iterators/sealedmonad/SealedTests.scala +++ b/sealedmonad/src/test/scala/pl/iterators/sealedmonad/SealedTests.scala @@ -62,12 +62,13 @@ trait SealedTests[F[_]] extends Laws with SealedTestInstances { "ensureOrF consistent with ensureF" -> forAll(laws.ensureOrFEnsureFCoherence[A, ADT] _), "ensureOrF true identity" -> forAll(laws.ensureOrFTrueIdentity[A, ADT] _), "ensureOrF false consistent with completeWith" -> forAll(laws.ensureOrFFalseIdentity[A, ADT] _), + "ensureNotOrF true identity" -> forAll(laws.ensureNotOrFTrueIdentity[A, ADT] _), + "ensureNotOrF false consistent with completeWith" -> forAll(laws.ensureNotOrFFalseIdentity[A, ADT] _), "either identity" -> forAll(laws.eitherIdentity[A, ADT] _), "foldM consistent with flatMap" -> forAll(laws.foldMCoherentWithFlatMap[A, ADT] _), "inspect does not change instance" -> forAll(laws.inspectElimination[A, B, ADT] _), "valueOr" -> forAll(laws.valueOrIdentity[A, ADT] _), "handleError" -> forAll(laws.handleErrorIdentity[A, B, ADT] _), - "biMap" -> forAll(laws.bimapIdentity[A, B, ADT, C] _), "semiflatMap stack-safety" -> lzy(laws.semiflatMapStackSafety) // "map stack-safety" -> lzy(laws.computationMapStackSafety) ) diff --git a/sealedmonad/src/test/scala/pl/iterators/sealedmonad/laws/SealedLaws.scala b/sealedmonad/src/test/scala/pl/iterators/sealedmonad/laws/SealedLaws.scala index e22bae4..4ff1291 100644 --- a/sealedmonad/src/test/scala/pl/iterators/sealedmonad/laws/SealedLaws.scala +++ b/sealedmonad/src/test/scala/pl/iterators/sealedmonad/laws/SealedLaws.scala @@ -55,6 +55,9 @@ trait SealedLaws[F[_]] { def ensureOrFTrueIdentity[A, B](s: Sealed[F, A, B], b: A => F[B]) = s.ensureOrF(_ => true, b) <-> s def ensureOrFFalseIdentity[A, B](s: Sealed[F, A, B], b: A => F[B]) = s.ensureOrF(_ => false, b) <-> s.completeWith(b) + def ensureNotOrFTrueIdentity[A, B](s: Sealed[F, A, B], b: A => F[B]) = s.ensureNotOrF(_ => true, b) <-> s.completeWith(b) + def ensureNotOrFFalseIdentity[A, B](s: Sealed[F, A, B], b: A => F[B]) = s.ensureNotOrF(_ => false, b) <-> s + def foldMCoherentWithFlatMap[A, B](fa: F[Option[A]], b: B) = Sealed(fa).attempt(Either.fromOption(_, b)).foldM[Int, B](_ => Sealed.liftF(0), _ => Sealed.liftF(1)) <-> Sealed(fa).flatMap { case None => Sealed.liftF(0) @@ -83,9 +86,6 @@ trait SealedLaws[F[_]] { def handleErrorIdentity[A, B, C](fab: F[Either[A, B]], f: A => C) = Sealed.handleError(fab)(f) <-> Sealed(fab).attempt(_.leftMap(f)) - def bimapIdentity[A, B, C, D](fab: F[Either[A, B]], f: A => C, fb: B => D) = - Sealed.bimap(fab)(f)(fb) <-> Sealed(fab).attempt(_.leftMap(f).map(fb)) - lazy val semiflatMapStackSafety = { val n = 50000 From 7c7afd1333d3d29530a9774bbac66244d3e4de85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Sowa?= Date: Thu, 17 Oct 2024 12:03:09 +0200 Subject: [PATCH 2/3] Improving naming in sbt. Uncommenting test. --- build.sbt | 12 ++++++------ .../scala/pl/iterators/sealedmonad/SealedTests.scala | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index 0710db7..6188a44 100644 --- a/build.sbt +++ b/build.sbt @@ -93,7 +93,7 @@ lazy val noPublishSettings = lazy val examples = project .in(file("examples")) - .dependsOn(sealedMonad.jvm % "test->test;compile->compile") + .dependsOn(core.jvm % "test->test;compile->compile") .settings(baseSettings *) .settings(noPublishSettings *) .settings( @@ -104,7 +104,7 @@ lazy val examples = project lazy val docs = project .in(file("sealed-docs")) - .dependsOn(sealedMonad.jvm % "test->test;compile->compile") + .dependsOn(core.jvm % "test->test;compile->compile") .enablePlugins(MdocPlugin, DocusaurusPlugin) .settings(baseSettings *) .settings(noPublishSettings *) @@ -121,7 +121,7 @@ lazy val docs = project lazy val benchmarks = project .in(file("benchmarks")) - .dependsOn(sealedMonad.jvm % "test->test;compile->compile") + .dependsOn(core.jvm % "test->test;compile->compile") .enablePlugins(JmhPlugin) .settings(baseSettings *) .settings(noPublishSettings *) @@ -133,7 +133,7 @@ lazy val benchmarks = project addCommandAlias("flame", "benchmarks/jmh:run -p tokens=64 -prof jmh.extras.Async:dir=target/flamegraphs;flameGraphOpts=--width,1900") -lazy val sealedMonad = crossProject(JSPlatform, JVMPlatform, NativePlatform) +lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform) .withoutSuffixFor(JVMPlatform) .crossType(CrossType.Pure) .in(file("sealedmonad")) @@ -144,6 +144,6 @@ lazy val sealedMonad = crossProject(JSPlatform, JVMPlatform, NativePlatform) description := "Scala library for nice for-comprehension-style error handling" ) -lazy val root = tlCrossRootProject - .aggregate(sealedMonad, benchmarks, docs, examples) +lazy val sealedMonad = tlCrossRootProject + .aggregate(core, benchmarks, docs, examples) .settings(baseSettings *) diff --git a/sealedmonad/src/test/scala/pl/iterators/sealedmonad/SealedTests.scala b/sealedmonad/src/test/scala/pl/iterators/sealedmonad/SealedTests.scala index 19be084..77d2941 100644 --- a/sealedmonad/src/test/scala/pl/iterators/sealedmonad/SealedTests.scala +++ b/sealedmonad/src/test/scala/pl/iterators/sealedmonad/SealedTests.scala @@ -69,8 +69,8 @@ trait SealedTests[F[_]] extends Laws with SealedTestInstances { "inspect does not change instance" -> forAll(laws.inspectElimination[A, B, ADT] _), "valueOr" -> forAll(laws.valueOrIdentity[A, ADT] _), "handleError" -> forAll(laws.handleErrorIdentity[A, B, ADT] _), - "semiflatMap stack-safety" -> lzy(laws.semiflatMapStackSafety) - // "map stack-safety" -> lzy(laws.computationMapStackSafety) + "semiflatMap stack-safety" -> lzy(laws.semiflatMapStackSafety), + "map stack-safety" -> lzy(laws.computationMapStackSafety) ) } From b4a5ef80e5108265f37ca327006520aaee09dc68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Sowa?= Date: Thu, 17 Oct 2024 12:06:48 +0200 Subject: [PATCH 3/3] ci.yml update --- .github/workflows/ci.yml | 58 ++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5821ca6..e7c8ebb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,11 +30,11 @@ jobs: os: [ubuntu-latest] scala: [2.13, 3] java: [temurin@11, temurin@17] - project: [rootJS, rootJVM, rootNative] + project: [sealedMonadNative, sealedMonadJS, sealedMonadJVM] exclude: - - project: rootJS + - project: sealedMonadNative java: temurin@17 - - project: rootNative + - project: sealedMonadJS java: temurin@17 runs-on: ${{ matrix.os }} timeout-minutes: 60 @@ -76,14 +76,14 @@ jobs: - name: Check that workflows are up to date run: sbt githubWorkflowCheck - - name: scalaJSLink - if: matrix.project == 'rootJS' - run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/scalaJSLinkerResult - - name: nativeLink - if: matrix.project == 'rootNative' + if: matrix.project == 'sealedMonadNative' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/nativeLink + - name: scalaJSLink + if: matrix.project == 'sealedMonadJS' + run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/scalaJSLinkerResult + - name: Test run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test @@ -97,11 +97,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master') - run: mkdir -p sealedmonad/.native/target sealedmonad/.jvm/target sealedmonad/.js/target project/target + run: mkdir -p sealedmonad/.native/target sealedmonad/.js/target sealedmonad/.jvm/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master') - run: tar cf targets.tar sealedmonad/.native/target sealedmonad/.jvm/target sealedmonad/.js/target project/target + run: tar cf targets.tar sealedmonad/.native/target sealedmonad/.js/target sealedmonad/.jvm/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master') @@ -154,62 +154,62 @@ jobs: if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' run: sbt +update - - name: Download target directories (2.13, rootJS) + - name: Download target directories (2.13, sealedMonadNative) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-sealedMonadNative - - name: Inflate target directories (2.13, rootJS) + - name: Inflate target directories (2.13, sealedMonadNative) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13, rootJVM) + - name: Download target directories (2.13, sealedMonadJS) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-sealedMonadJS - - name: Inflate target directories (2.13, rootJVM) + - name: Inflate target directories (2.13, sealedMonadJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13, rootNative) + - name: Download target directories (2.13, sealedMonadJVM) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootNative + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-sealedMonadJVM - - name: Inflate target directories (2.13, rootNative) + - name: Inflate target directories (2.13, sealedMonadJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3, rootJS) + - name: Download target directories (3, sealedMonadNative) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-3-sealedMonadNative - - name: Inflate target directories (3, rootJS) + - name: Inflate target directories (3, sealedMonadNative) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3, rootJVM) + - name: Download target directories (3, sealedMonadJS) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-3-sealedMonadJS - - name: Inflate target directories (3, rootJVM) + - name: Inflate target directories (3, sealedMonadJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3, rootNative) + - name: Download target directories (3, sealedMonadJVM) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootNative + name: target-${{ matrix.os }}-${{ matrix.java }}-3-sealedMonadJVM - - name: Inflate target directories (3, rootNative) + - name: Inflate target directories (3, sealedMonadJVM) run: | tar xf targets.tar rm targets.tar @@ -284,5 +284,5 @@ jobs: - name: Submit Dependencies uses: scalacenter/sbt-dependency-submission@v2 with: - modules-ignore: root_2.13 root_3 rootjs_2.13 rootjs_3 rootjvm_2.13 rootjvm_3 rootnative_2.13 rootnative_3 + modules-ignore: sealedmonadnative_2.13 sealedmonadnative_3 sealedmonad_2.13 sealedmonad_3 sealedmonadjs_2.13 sealedmonadjs_3 sealedmonadjvm_2.13 sealedmonadjvm_3 configs-ignore: test scala-tool scala-doc-tool test-internal