diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..1fca28a --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Scala Steward: Reformat with scalafmt 3.7.17 +7c6966fbfe8e67006302d992d51bef38713be851 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25cc9ae..7bf7677 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.12.17, 2.13.10, 3.2.2] + scala: [2.12.18, 2.13.12, 3.3.1] java: [temurin@8, temurin@17] runs-on: ${{ matrix.os }} steps: @@ -61,4 +61,4 @@ jobs: with: fetch-depth: 0 - uses: olafurpg/setup-scala@v13 - - run: sbt '++2.12.17 docs/mdoc' + - run: sbt '++2.12.18 docs/mdoc' diff --git a/.scalafmt.conf b/.scalafmt.conf index 447b2fd..f4b59b7 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -7,4 +7,4 @@ newlines.topLevelStatementBlankLines = [ ] rewrite.rules = [SortImports, RedundantBraces] runner.dialect = scala213 -version=3.7.3 +version=3.7.17 diff --git a/build.sbt b/build.sbt index cdd7937..eb742a1 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,8 @@ - val isDotty = Def.setting(CrossVersion.partialVersion(scalaVersion.value).exists(_._1 != 2)) // Dependencies -val catsVersion = "2.9.0" +val catsVersion = "2.10.0" val castsTestkitScalatestVersion = "2.1.5" libraryDependencies ++= Seq( @@ -19,18 +18,18 @@ libraryDependencies ++= (if (isDotty.value) Nil // Multiple Scala versions support -val scala_2_12 = "2.12.17" -val scala_2_13 = "2.13.10" -val dotty = "3.2.2" +val scala_2_12 = "2.12.18" +val scala_2_13 = "2.13.12" +val dotty = "3.3.1" val mainScalaVersion = scala_2_13 val supportedScalaVersions = Seq(scala_2_12, scala_2_13, dotty) ThisBuild / crossScalaVersions := supportedScalaVersions -ThisBuild / scalaVersion := mainScalaVersion +ThisBuild / scalaVersion := mainScalaVersion lazy val baseSettings = Seq( // Scala settings - homepage := Some(url("https://github.com/theiterators/sealed-monad")), + homepage := Some(url("https://github.com/theiterators/sealed-monad")), scalacOptions := Seq("-deprecation", "-unchecked", "-feature", "-encoding", "utf8") ++ (if (isDotty.value) Seq("-language:implicitConversions", "-Ykind-projector", "-Xignore-scala2-macros") @@ -62,13 +61,13 @@ lazy val baseSettings = Seq( connection = "scm:git:https://github.com/theiterators/sealed-monad.git" ) ), - crossScalaVersions := supportedScalaVersions + crossScalaVersions := supportedScalaVersions ) lazy val noPublishSettings = Seq( - publishArtifact := false, - skip / publish := true + publishArtifact := false, + skip / publish := true ) lazy val examples = project diff --git a/project/build.properties b/project/build.properties index 46e43a9..abbbce5 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.8.2 +sbt.version=1.9.8 diff --git a/project/plugins.sbt b/project/plugins.sbt index fc98f04..7fb0d4b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,6 @@ -addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.4") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") -addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.11") +addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.6") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") +addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.10.0") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.8") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") \ No newline at end of file diff --git a/src/main/scala/pl/iterators/sealedmonad/Sealed.scala b/src/main/scala/pl/iterators/sealedmonad/Sealed.scala index 244a8bc..c7c0d3c 100644 --- a/src/main/scala/pl/iterators/sealedmonad/Sealed.scala +++ b/src/main/scala/pl/iterators/sealedmonad/Sealed.scala @@ -1,15 +1,17 @@ package pl.iterators.sealedmonad import cats._ -import Function.const -import cats.syntax.bitraverse._ -import scala.language.higherKinds +import cats.syntax.all._ + +import scala.Function.const sealed trait Sealed[F[_], +A, +ADT] { - protected def step[A1 >: A, ADT1 >: ADT](implicit F: Applicative[F]): F[Either[Sealed[F, A1, ADT1], Either[ADT1, A1]]] + import Sealed._ + def map[B](f: A => B): Sealed[F, B, ADT] = Transform(this, f.andThen(left[F, B, ADT]), right[F, B, ADT]) + def flatMap[B, ADT1 >: ADT](f: A => Sealed[F, B, ADT1]): Sealed[F, B, ADT1] = Transform(this, f, right[F, B, ADT1]) - def map[B](f: A => B): Sealed[F, B, ADT] = Sealed.FlatMap(this, (a: A) => Sealed.Value(f(a))) - def flatMap[B, ADT1 >: ADT](f: A => Sealed[F, B, ADT1]): Sealed[F, B, ADT1] = Sealed.FlatMap(this, f) + /** Alias for map(_ => ()) */ + final def void: Sealed[F, Unit, ADT] = map(_ => ()) /** Transforms `A` to `B` using an effectful function. * @@ -26,13 +28,10 @@ sealed trait Sealed[F[_], +A, +ADT] { * res0: cats.Id[Response] = Value(42) * }}} */ - final def semiflatMap[B](f: A => F[B]): Sealed[F, B, ADT] = flatMap(a => Sealed.Effect(Eval.later(f(a)))) + final def semiflatMap[B](f: A => F[B]): Sealed[F, B, ADT] = Transform(this, f.andThen(leftF), right[F, B, ADT]) - final def leftSemiflatMap[ADT1](f: ADT => F[ADT1])(implicit F: Applicative[F]): Sealed[F, A, ADT1] = - either - .semiflatMap(_.leftTraverse(f)) - .asInstanceOf[Sealed[F, Either[ADT1, A], ADT1]] - .rethrow + final def leftSemiflatMap[ADT1 >: ADT](f: ADT => F[ADT1]): Sealed[F, A, ADT1] = + Transform(this, left[F, A, ADT1], f.andThen(rightF)) /** Executes a side effect if ADT has been reached, and returns unchanged `Sealed[F, A, ADT]`. * @@ -53,14 +52,17 @@ sealed trait Sealed[F[_], +A, +ADT] { * // prints 'left' * }}} */ - final def leftSemiflatTap[C](f: ADT => F[C])(implicit F: Applicative[F]): Sealed[F, A, ADT] = - leftSemiflatMap(adt => F.as(f(adt), adt)) - - final def biSemiflatMap[B, ADT1](fa: ADT => F[ADT1], fb: A => F[B])(implicit F: Applicative[F]): Sealed[F, B, ADT1] = - either - .semiflatMap(_.bitraverse(fa, fb)) - .asInstanceOf[Sealed[F, Either[ADT1, B], ADT1]] - .rethrow + final def leftSemiflatTap[C](f: ADT => F[C]): Sealed[F, A, ADT] = + Transform( + this, + left[F, A, ADT], + (adt: ADT) => Transform(leftF(f(adt)), (_: C) => right[F, A, ADT](adt), (_: Any) => right(adt)) + ) + + /** Combine leftSemiflatMap and semiflatMap together. + */ + final def biSemiflatMap[B, ADT1 >: ADT](fa: ADT => F[ADT1], fb: A => F[B]): Sealed[F, B, ADT1] = + Transform(this, (a: A) => leftF[F, B, ADT1](fb(a)), (adt: ADT) => rightF[F, B, ADT1](fa(adt))) /** Executes appropriate side effect depending on whether `A` or `ADT` has been reached, and returns unchanged `Sealed[F, A, ADT]`. * @@ -82,8 +84,12 @@ sealed trait Sealed[F[_], +A, +ADT] { * // prints 'left' * }}} */ - final def biSemiflatTap[B, C](fa: ADT => F[C], fb: A => F[B])(implicit F: Applicative[F]): Sealed[F, A, ADT] = - biSemiflatMap[A, ADT](adt => F.as(fa(adt), adt), a => F.as(fb(a), a)) + final def biSemiflatTap[B, C](fa: ADT => F[C], fb: A => F[B]): Sealed[F, A, ADT] = + Transform( + this, + (a: A) => Transform(leftF[F, B, ADT](fb(a)), (_: B) => left[F, A, ADT](a), (adt: ADT) => right[F, A, ADT](adt)), + (adt: ADT) => Transform(leftF[F, C, ADT](fa(adt)), (_: C) => right[F, A, ADT](adt), (adt: ADT) => right[F, A, ADT](adt)) + ) /** Finishes the computation by returning Sealed with given ADT. * @@ -101,7 +107,7 @@ sealed trait Sealed[F[_], +A, +ADT] { * res0: cats.Id[Response] = Transformed(2) * }}} */ - final def complete[ADT1 >: ADT](f: A => ADT1): Sealed[F, Nothing, ADT1] = flatMap(a => Sealed.Result(f(a))) + final def complete[ADT1 >: ADT](f: A => ADT1): Sealed[F, Nothing, ADT1] = flatMap(a => right(f(a))) /** Effectful version of `complete`. * @@ -119,13 +125,13 @@ sealed trait Sealed[F[_], +A, +ADT] { * res0: cats.Id[Response] = Transformed(2) * }}} */ - final def completeWith[ADT1 >: ADT](f: A => F[ADT1]): Sealed[F, Nothing, ADT1] = flatMap(a => Sealed.ResultF(Eval.later(f(a)))) + 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 * usage. */ final def rethrow[B, ADT1 >: ADT](implicit ev: A <:< Either[ADT1, B]): Sealed[F, B, ADT1] = - flatMap(a => ev(a).fold(Sealed.Result(_), Sealed.Value(_))) + flatMap(a => ev(a).fold(right, left)) /** Converts `A` into `Either[ADT1, B]` and creates a Sealed instance from the result. * @@ -192,8 +198,8 @@ sealed trait Sealed[F[_], +A, +ADT] { * res1: cats.Id[Response] = Reached * }}} */ - final def foldM[B, ADT1 >: ADT](left: ADT1 => Sealed[F, B, ADT1], right: A => Sealed[F, B, ADT1]): Sealed[F, B, ADT1] = - Sealed.Fold(this, left, right) + final def foldM[B, ADT1 >: ADT](left: ADT => Sealed[F, B, ADT1], right: A => Sealed[F, B, ADT1]): Sealed[F, B, ADT1] = + Transform(this, right, left) /** Converts `A` into `Either[ADT, A]`. Usually paired with `rethrow`. * @@ -207,14 +213,15 @@ 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.either.map(_.fold(adt => Left(Transformed(2)), number => Right(42))).rethrow } yield Value(x)).run + * scala> (for { x <- sealedSome.either.map(_.fold(adt => Intermediate(Transformed(2)), number => Result(42))).rethrow } yield Value(x)).run * val res0: cats.Id[Response] = Value(42) * scala> val sealedNone: Sealed[Id, Int, Response] = Id(Option.empty).valueOr(NotFound) - * scala> (for { x <- sealedNone.either.map(_.fold(adt => Left(Transformed(2)), number => Right(42))).rethrow } yield Value(x)).run + * scala> (for { x <- sealedNone.either.map(_.fold(adt => Intermediate(Transformed(2)), number => Result(42))).rethrow } yield Value(x)).run * val res1: cats.Id[Response] = Transformed(2) * }}} */ - final def either: Sealed[F, Either[ADT, A], ADT] = foldM((adt: ADT) => Sealed.Value(Left(adt)), a => Sealed.Value(Right(a))) + final def either: Sealed[F, Either[ADT, A], ADT] = + foldM(adt => left(Either.left(adt)), a => left(Either.right(a))) /** Executes a fire-and-forget side effect and returns unchanged `Sealed[F, A, ADT]`. Works irrespectively of Sealed's current state, in * contrary to `tap`. Useful for logging purposes. @@ -240,6 +247,30 @@ sealed trait Sealed[F[_], +A, +ADT] { final def inspect(pf: PartialFunction[Either[ADT, A], Any]): Sealed[F, A, ADT] = either.map(e => pf.andThen(const(e)(_)).applyOrElse(e, const(e))).rethrow + /** Executes an effect F and returns unchanged `Sealed[F, A, ADT]`. Works irrespectively of Sealed's current state, in contrary to `tap`. + * Useful for effectful logging purposes. + * + * Example: + * {{{ + * scala> import pl.iterators.sealedmonad.Sealed + * scala> import pl.iterators.sealedmonad.syntax._ + * scala> import cats.Eval + * scala> sealed trait Response + * scala> case class Value(i: Int) extends Response + * scala> case object NotFound extends Response + * scala> val sealedSome: Sealed[Eval, Int, Response] = Eval.later(Option(1)).valueOr(NotFound) + * scala> (for { x <- sealedSome.inspectF(either => either.fold(adt => Eval.later(println(adt)), number => Eval.later(println(number)))) } yield Value(x)).run.value + * val res0: Value = Value(1) + * // prints '1' + * scala> val sealedNone: Sealed[Eval, Int, Response] = Eval.later(Option.empty).valueOr(NotFound) + * scala> (for { x <- sealedNone.inspectF(either => either.fold(adt => Eval.later(println(adt)), number => Eval.later(println(number)))) } yield Value(x)).run.value + * val res1: Response = NotFound + * // prints 'NotFound' + * }}} + */ + 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`. * @@ -317,9 +348,8 @@ sealed trait Sealed[F[_], +A, +ADT] { * res2: cats.Id[Response] = NotFound * }}} */ - final def ensureOrF[ADT1 >: ADT](pred: A => Boolean, orElse: A => F[ADT1]): Sealed[F, A, ADT1] = - flatMap(a => if (pred(a)) Sealed.Value(a) else completeWith(orElse)) + flatMap(a => if (pred(a)) left(a) else completeWith(orElse)) /** Effectful version of `ensure`. * @@ -342,7 +372,6 @@ sealed trait Sealed[F[_], +A, +ADT] { * res2: cats.Id[Response] = NotFound * }}} */ - final def ensureF[ADT1 >: ADT](pred: A => Boolean, orElse: => F[ADT1]): Sealed[F, A, ADT1] = ensureOrF(pred, _ => orElse) @@ -414,87 +443,118 @@ 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 Sealed.Value(a)) + flatMap(a => if (cond(a)) flatTap(f) else left(a)) - final def run[ADT1 >: ADT](implicit coerce: A <:< ADT1, F: Monad[F]): F[ADT1] = F.fmap(F.tailRecM(this)(_.step))(_.map(coerce).merge) + final def run[ADT1 >: ADT](implicit ev: A <:< ADT1, F: Monad[F]): F[ADT1] = eval(this).map(_.fold(ev, identity)) } object Sealed extends SealedInstances { - private[sealedmonad] final case class FlatMap[F[_], A0, A, ADT](current: Sealed[F, A0, ADT], cont: A0 => Sealed[F, A, ADT]) - extends Sealed[F, A, ADT] { - def runCont[B](f: B => Sealed[F, A0, ADT]) = f andThen (_.flatMap(cont)) - - override def step[A1 >: A, ADT1 >: ADT](implicit F: Applicative[F]) = - current match { - case Value(a) => F.pure(Left(cont(a))) - case Effect(fa) => F.fmap(fa.value)(a0 => Left(cont(a0))) - case FlatMap(prev, g) => F.pure(Left(prev.flatMap(runCont(g)))) - case Fold(prev, l, r) => F.pure(Left(Fold(prev, runCont(l.asInstanceOf[ADT => Sealed[F, A0, ADT]]), runCont(r)))) - case _ => sys.error("impossible") - } - } + import cats.syntax.either._ - private[sealedmonad] final case class Effect[F[_], A](fa: Eval[F[A]]) extends Sealed[F, A, Nothing] { - override protected def step[A1 >: A, ADT1 >: Nothing](implicit F: Applicative[F]) = F.fmap(fa.value)(a => Right(Right(a))) - } + 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)) - private[sealedmonad] final case class ResultF[F[_], ADT](fadt: Eval[F[ADT]]) extends Sealed[F, Nothing, ADT] { - override def map[B](f: Nothing => B) = this - override def flatMap[B, ADT1 >: ADT](f: Nothing => Sealed[F, B, ADT1]) = this - override protected def step[A1 >: Nothing, ADT1 >: ADT](implicit F: Applicative[F]) = F.fmap(fadt.value)(adt => Right(Left(adt))) - } + def seal[F[_], A](value: A): Sealed[F, Nothing, A] = defer(right(value)) - private[sealedmonad] final case class Result[F[_], ADT](result: ADT) extends Sealed[F, Nothing, ADT] { - override def map[B](f: Nothing => B) = this - override def flatMap[B, ADT1 >: ADT](f: Nothing => Sealed[F, B, ADT1]) = this - override def step[A1 >: Nothing, ADT1 >: ADT](implicit F: Applicative[F]) = F.pure(Right(Left(result))) - } + def result[F[_], ADT](value: => F[ADT]): Sealed[F, Nothing, ADT] = defer(rightF(value)) - private[sealedmonad] final case class Value[F[_], A](a: A) extends Sealed[F, A, Nothing] { - override def step[A1 >: A, ADT1 >: Nothing](implicit F: Applicative[F]) = F.pure(Right(Right(a))) + 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) } - private[sealedmonad] final case class Fold[F[_], A0, A, ADT]( - value: Sealed[F, A0, ADT], - left: ADT => Sealed[F, A, ADT], - right: A0 => Sealed[F, A, ADT] - ) extends Sealed[F, A, ADT] { - def runFold[B](f: B => Sealed[F, A0, ADT]) = f andThen (_.foldM(left, right)) - - override def step[A1 >: A, ADT1 >: ADT](implicit F: Applicative[F]): F[Either[Sealed[F, A1, ADT1], Either[ADT1, A1]]] = - value match { - case Result(adt) => F.pure(Left(left(adt))) - case ResultF(fadt) => F.fmap(fadt.value)(adt => Left(left(adt))) - case Value(a) => F.pure(Left(right(a))) - case Effect(fa) => F.fmap(fa.value)(a0 => Left(right(a0))) - case FlatMap(prev, f) => F.pure(Left(prev.flatMap(runFold(f)))) - case Fold(v0, l0, r0) => F.pure(Left(Fold(v0, runFold(l0.asInstanceOf[ADT => Sealed[F, A0, ADT]]), runFold(r0)))) - } - } + 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) + } - import cats.syntax.either._ + def handleError[F[_], A, B, ADT](fa: F[Either[A, B]])(f: A => ADT): Sealed[F, B, ADT] = apply(fa).attempt(_.leftMap(f)) - def liftF[F[_], A](value: A): Sealed[F, A, Nothing] = Value(value) - def apply[F[_], A](value: => F[A]): Sealed[F, A, Nothing] = Effect(Eval.later(value)) + 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)) - def seal[F[_], A](value: A): Sealed[F, Nothing, A] = Result(value) + /** Represents either an intermediate A or a final ADT. + */ + private final case class Pure[F[_], A, ADT]( + value: Either[A, ADT] + ) extends Sealed[F, A, ADT] - def result[F[_], ADT](value: => F[ADT]): Sealed[F, Nothing, ADT] = ResultF(Eval.later(value)) + /** Represents an intermediate F[A] or a final F[ADT]. + */ + private final case class Suspend[F[_], A, ADT]( + fa: Either[F[A], F[ADT]] + ) extends Sealed[F, A, ADT] - def valueOr[F[_], A, ADT](fa: => F[Option[A]], orElse: => ADT): Sealed[F, A, ADT] = Sealed(fa).attempt(Either.fromOption(_, orElse)) + /** Represents a deferred computation. + */ + private final case class Defer[F[_], A, ADT]( + value: () => Sealed[F, A, ADT] + ) extends Sealed[F, A, ADT] - def valueOrF[F[_], A, ADT](fa: => F[Option[A]], orElse: => F[ADT]): Sealed[F, A, ADT] = - Sealed(fa).flatMap { - case Some(a) => liftF(a) - case None => result(orElse) + /** Represents a transformation on either intermediate A0 or final ADT0 value. + * + * Mind that the naming here might be a bit confusing because `left` is a transformation that is applied when we haven't reached the + * final ADT yet, and `right` is a transformation that is applied when we have reached the final ADT. + * + * On the user side Sealed behaves similar to EitherT, so `left` applies to final ADT and right applies to intermediate A. See `foldM` + * for an example. + */ + private final case class Transform[F[_], A0, A, ADT0, ADT]( + current: Sealed[F, A0, ADT0], + left: A0 => Sealed[F, A, ADT], + right: ADT0 => Sealed[F, A, ADT] + ) extends Sealed[F, A, ADT] + + private def left[F[_], A, ADT](value: A): Sealed[F, A, ADT] = Pure(Left(value)) + private def leftF[F[_], A, ADT](value: F[A]): Sealed[F, A, ADT] = Suspend(Left(value)) + private def right[F[_], A, ADT](value: ADT): Sealed[F, A, ADT] = Pure(Right(value)) + private def rightF[F[_], A, ADT](value: F[ADT]): Sealed[F, A, ADT] = Suspend(Right(value)) + private def defer[F[_], A, ADT](thunk: => Sealed[F, A, ADT]) = Defer(() => thunk) + + /** Does the heavy lifting. There's a trampoline to advance only one step forward. Transform is unrolled and rewritten to avoid nested + * functions and offer stack safety. + */ + private def eval[F[_], A, ADT](value: Sealed[F, A, ADT])(implicit F: Monad[F]): F[Either[A, ADT]] = { + type Intermediate = Sealed[F, A, ADT] + type Final = Either[A, ADT] + def recur(value: Intermediate): F[Either[Intermediate, Final]] = value.asLeft[Final].pure[F] + def returns(value: Final): F[Either[Intermediate, Final]] = value.asRight[Intermediate].pure[F] + value.tailRecM { + case Pure(either) => returns(either) + case Suspend(Left(fa)) => fa.flatMap(a => returns(a.asLeft[ADT])) + case Suspend(Right(fadt)) => fadt.flatMap(adt => returns(adt.asRight[A])) + case Defer(value) => recur(value()) + case Transform(current, onA, onADT) => + current match { + case Pure(Left(a)) => recur(onA(a)) + case Pure(Right(adt)) => recur(onADT(adt)) + case Suspend(Left(fa)) => fa.flatMap(a => recur(Transform(Pure(Left(a)), onA, onADT))) + 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.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.asInstanceOf[Any => Sealed[F, Any, Any]](adt0)), + onA.asInstanceOf[Any => Sealed[F, A, ADT]], + onADT.asInstanceOf[Any => Sealed[F, A, ADT]] + ) + ) + ) + } } - - def handleError[F[_], A, B, ADT](fa: F[Either[A, B]])(f: A => ADT): Sealed[F, B, ADT] = Sealed(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] = - Sealed(fa).attempt(_.leftMap(f).map(fb)) - + } } private final class SealedMonad[F[_], ADT] extends StackSafeMonad[Sealed[F, *, ADT]] { diff --git a/src/main/scala/pl/iterators/sealedmonad/syntax/SealedSyntax.scala b/src/main/scala/pl/iterators/sealedmonad/syntax/SealedSyntax.scala index f61fb4e..4e07361 100644 --- a/src/main/scala/pl/iterators/sealedmonad/syntax/SealedSyntax.scala +++ b/src/main/scala/pl/iterators/sealedmonad/syntax/SealedSyntax.scala @@ -20,8 +20,8 @@ final class SealedFAOps[F[_], A](private val self: F[A]) extends AnyVal { 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) def ensureOrF[ADT](pred: A => Boolean, orElse: A => F[ADT]): Sealed[F, A, ADT] = seal[ADT].ensureOrF(pred, orElse) - def attempt[ADT, B](f: A => Either[ADT, B]): Sealed[F, B, ADT] = seal[ADT].attempt(f) - def attemptF[ADT, B](f: A => F[Either[ADT, B]]): Sealed[F, B, ADT] = seal[ADT].attemptF(f) + 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) } final class SealedFOptAOps[F[_], A](private val self: F[Option[A]]) extends AnyVal { diff --git a/src/test/scala/pl/iterators/sealedmonad/SealedSpecs.scala b/src/test/scala/pl/iterators/sealedmonad/SealedSpecs.scala index 01da33a..03a8b34 100644 --- a/src/test/scala/pl/iterators/sealedmonad/SealedSpecs.scala +++ b/src/test/scala/pl/iterators/sealedmonad/SealedSpecs.scala @@ -1,6 +1,11 @@ package pl.iterators.sealedmonad -import pl.iterators.sealedmonad.side_effects.{SealedSideEffectsInspect, SealedSideEffectsSemiflatTap, SealedSideEffectsTap} +import pl.iterators.sealedmonad.side_effects.{ + SealedSideEffectsInspect, + SealedSideEffectsInspectF, + SealedSideEffectsSemiflatTap, + SealedSideEffectsTap +} class SealedSpecs extends SealedSuite { @@ -54,6 +59,54 @@ class SealedSpecs extends SealedSuite { assert(SealedSideEffectsInspect.notInvokeOnSomeValueOrWithNegativeEnsure) } + test("inspectF side effect not invoked") { + assert(SealedSideEffectsInspectF.simplyInvoke) + } + + test("inspectF side effect invoked") { + assert(SealedSideEffectsInspectF.simplyNotInvoke) + } + + test("inspectF with valueOr with None side effect invoked") { + assert(SealedSideEffectsInspectF.invokeOnValueOrOnNone) + } + + test("inspectF with valueOr with None side effect not invoked") { + assert(SealedSideEffectsInspectF.notInvokeOnValueOrOnNone) + } + + test("inspectF with valueOr with Some side effect invoked") { + assert(SealedSideEffectsInspectF.invokeOnValueOrOnSome) + } + + test("inspectF with valueOr with Some side effect not invoked") { + assert(SealedSideEffectsInspectF.notInvokeOnValueOrOnSome) + } + + test("inspectF with positive ensure side effect invoked") { + assert(SealedSideEffectsInspectF.invokeOnPositiveEnsure) + } + + test("inspectF with positive ensure side effect not invoked") { + assert(SealedSideEffectsInspectF.notInvokeOnPositiveEnsure) + } + + test("inspectF with negative ensure negative side effect invoked") { + assert(SealedSideEffectsInspectF.invokeOnNegativeEnsure) + } + + test("inspectF with negative ensure negative side effect not invoked") { + assert(SealedSideEffectsInspectF.notInvokeOnNegativeEnsure) + } + + test("inspectF with Some valueOr with negative ensure negative side effect invoked") { + assert(SealedSideEffectsInspectF.invokeOnSomeValueOrWithNegativeEnsure) + } + + test("inspectF with Some valueOr with negative ensure negative side effect not invoked") { + assert(SealedSideEffectsInspectF.notInvokeOnSomeValueOrWithNegativeEnsure) + } + test("tap side effect invoked") { assert(SealedSideEffectsTap.invokeTap) } diff --git a/src/test/scala/pl/iterators/sealedmonad/SealedTests.scala b/src/test/scala/pl/iterators/sealedmonad/SealedTests.scala index d081326..91ace84 100644 --- a/src/test/scala/pl/iterators/sealedmonad/SealedTests.scala +++ b/src/test/scala/pl/iterators/sealedmonad/SealedTests.scala @@ -35,43 +35,40 @@ trait SealedTests[F[_]] extends Laws with SealedTestInstances { ) = new SimpleRuleSet( name = "combinators", - "value map" -> forAll(laws.valueMapReduction[A, B] _), - "result map short-circuits" -> forAll(laws.resultMapElimination[A, ADT] _), - "result flatMap short-circuits" -> forAll(laws.resultFlatMapElimination[A, ADT] _), - "semiflatMap consistent with flatMap" -> forAll(laws.valueSemiflatMapReduction[A, B] _), - "result semiflatMap short-circuits" -> forAll(laws.resultSemiflatMapElimination[A, ADT] _), - "leftSemiflatMap identity" -> forAll(laws.resultLeftSemiflatMapIdentity[A, ADT] _), - "leftSemiflatMap elimination" -> forAll(laws.valueLeftSemiflatMapElimination[A, ADT] _), - "biSemiflatTap coherent with identity" -> forAll(laws.biSemiflatTapCoherentWithIdentity[A, B, ADT] _), + "value map" -> forAll(laws.valueMapReduction[A, B] _), + "result map short-circuits" -> forAll(laws.resultMapElimination[A, ADT] _), + "result flatMap short-circuits" -> forAll(laws.resultFlatMapElimination[A, ADT] _), + "semiflatMap consistent with flatMap" -> forAll(laws.valueSemiflatMapReduction[A, B] _), + "result semiflatMap short-circuits" -> forAll(laws.resultSemiflatMapElimination[A, ADT] _), + "leftSemiflatMap elimination" -> forAll(laws.valueLeftSemiflatMapElimination[A, ADT] _), "result biSemiflatMap coherent with result leftsemiFlatMap + semiflatMap" -> forAll(laws.resultBiSemiflatMapCoherence[A, ADT, B] _), "value biSemiflatMap coherent with value leftsemiFlatMap + semiflatMap" -> forAll(laws.valueBiSemiflatMapCoherence[A, ADT, B] _), - "leftSemiflatTap coherent with identity" -> forAll(laws.leftSemiflatTapCoherentWithIdentity[A, B, ADT] _), - "complete consistent with result + flatMap" -> forAll(laws.valueCompleteIdentity[A, ADT] _), - "completeWith consistent with complete + unit" -> forAll(laws.completeWithCoherence[A, ADT] _), - "result complete short-circuits" -> forAll(laws.resultCompleteElimination[A, ADT] _), - "rethrow right does not change value" -> forAll(laws.rethrowRightIdentity[A, ADT] _), - "rethrow left consistent with complete" -> forAll(laws.rethrowLeftIdentity[ADT] _), - "attempt right consistent with map" -> forAll(laws.attemptRightIdentity[A, B, ADT] _), - "attempt left consistent with complete" -> forAll(laws.attemptLeftIdentity[A, ADT] _), - "attemptF consistent with attempt + unit" -> forAll(laws.attemptFCoherence[A, B, ADT] _), - "ensure true identity" -> forAll(laws.ensureTrueIdentity[A, ADT] _), - "ensure false consistent with complete" -> forAll(laws.ensureFalseIdentity[A, ADT] _), - "ensure consistent with ensureNot" -> forAll(laws.ensureCoherence[A, ADT] _), - "ensure consistent with rethrow" -> forAll(laws.ensureRethrowCoherence[A, ADT] _), - "ensureF true identity" -> forAll(laws.ensureFTrueIdentity[A, ADT] _), - "ensureF false consistent with completeWith" -> forAll(laws.ensureFFalseIdentity[A, ADT] _), - "ensureF consistent with attemptF" -> forAll(laws.ensureFAttemptFCoherence[A, ADT] _), - "ensureF consistent with ensure" -> forAll(laws.ensureFEnsureCoherence[A, ADT] _), - "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] _), - "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) + "complete consistent with result + flatMap" -> forAll(laws.valueCompleteIdentity[A, ADT] _), + "completeWith consistent with complete + unit" -> forAll(laws.completeWithCoherence[A, ADT] _), + "result complete short-circuits" -> forAll(laws.resultCompleteElimination[A, ADT] _), + "rethrow right does not change value" -> forAll(laws.rethrowRightIdentity[A, ADT] _), + "rethrow left consistent with complete" -> forAll(laws.rethrowLeftIdentity[ADT] _), + "attempt right consistent with map" -> forAll(laws.attemptRightIdentity[A, B, ADT] _), + "attempt left consistent with complete" -> forAll(laws.attemptLeftIdentity[A, ADT] _), + "attemptF consistent with attempt + unit" -> forAll(laws.attemptFCoherence[A, B, ADT] _), + "ensure true identity" -> forAll(laws.ensureTrueIdentity[A, ADT] _), + "ensure false consistent with complete" -> forAll(laws.ensureFalseIdentity[A, ADT] _), + "ensure consistent with ensureNot" -> forAll(laws.ensureCoherence[A, ADT] _), + "ensure consistent with rethrow" -> forAll(laws.ensureRethrowCoherence[A, ADT] _), + "ensureF true identity" -> forAll(laws.ensureFTrueIdentity[A, ADT] _), + "ensureF false consistent with completeWith" -> forAll(laws.ensureFFalseIdentity[A, ADT] _), + "ensureF consistent with attemptF" -> forAll(laws.ensureFAttemptFCoherence[A, ADT] _), + "ensureF consistent with ensure" -> forAll(laws.ensureFEnsureCoherence[A, ADT] _), + "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] _), + "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/src/test/scala/pl/iterators/sealedmonad/laws/SealedLaws.scala b/src/test/scala/pl/iterators/sealedmonad/laws/SealedLaws.scala index b67c734..5fede94 100644 --- a/src/test/scala/pl/iterators/sealedmonad/laws/SealedLaws.scala +++ b/src/test/scala/pl/iterators/sealedmonad/laws/SealedLaws.scala @@ -22,28 +22,15 @@ trait SealedLaws[F[_]] { def valueSemiflatMapReduction[A, B](fa: F[A], f: A => F[B]) = Sealed(fa).semiflatMap(f) <-> Sealed(fa >>= f) def resultSemiflatMapElimination[A, B](fb: F[B], f: A => F[B]) = Sealed.result(fb).semiflatMap(f) <-> Sealed.result(fb) - def resultLeftSemiflatMapIdentity[A, B](fa: F[A], fab: A => F[B]) = - Sealed.result(fa).leftSemiflatMap(fab) <-> Sealed.result(fa >>= fab) - def valueLeftSemiflatMapElimination[A, B](fa: F[A], fab: A => F[B]) = Sealed(fa).leftSemiflatMap(fab) <-> Sealed(fa) def resultBiSemiflatMapCoherence[A, B, C](fa: F[A], fab: A => F[B], fcd: A => F[C]) = - Sealed.result(fa).biSemiflatMap(fab, fcd) <-> Sealed.result(fa).leftSemiflatMap(fab).semiflatMap(fcd) + Sealed.apply(fa).biSemiflatMap(fab, fcd) <-> Sealed.apply(fa).leftSemiflatMap(fab).semiflatMap(fcd) def valueBiSemiflatMapCoherence[A, B, C](fa: F[A], fab: A => F[B], fcd: A => F[C]) = Sealed(fa).biSemiflatMap(fab, fcd) <-> Sealed(fa).leftSemiflatMap(fab).semiflatMap(fcd) - def biSemiflatTapCoherentWithIdentity[A, B, C](fa: F[Option[A]], b: C) = - Sealed(fa).attempt(Either.fromOption(_, b)).biSemiflatTap[Int, Int](_ => M.pure(0), _ => M.pure(1)) <-> Sealed(fa).attempt( - Either.fromOption(_, b) - ) - - def leftSemiflatTapCoherentWithIdentity[A, B, C](fa: F[Option[A]], b: C) = - Sealed(fa).attempt(Either.fromOption(_, b)).leftSemiflatTap[Int](_ => M.pure(0)) <-> Sealed(fa).attempt( - Either.fromOption(_, b) - ) - def valueCompleteIdentity[A, B](fa: F[A], f: A => F[B]) = Sealed(fa).completeWith(f) <-> Sealed.result(fa >>= f) def resultCompleteElimination[A, B](fb: F[B], f: A => B) = Sealed.result(fb).complete(f) <-> Sealed.result(fb) diff --git a/src/test/scala/pl/iterators/sealedmonad/side_effects/SealedSideEffectsInspectF.scala b/src/test/scala/pl/iterators/sealedmonad/side_effects/SealedSideEffectsInspectF.scala new file mode 100644 index 0000000..b53f43f --- /dev/null +++ b/src/test/scala/pl/iterators/sealedmonad/side_effects/SealedSideEffectsInspectF.scala @@ -0,0 +1,326 @@ +package pl.iterators.sealedmonad.side_effects + +import cats.Eval +import pl.iterators.sealedmonad.syntax._ + +trait SealedSideEffectsInspectF { + + def simplyInvoke: Boolean = { + var invoked: Boolean = false + var invokedOther: Boolean = false + + def invoke() = + Eval.later { invoked = true } + + def invokeOther() = + Eval.later { invokedOther = true } + + 0 + .seal[Eval] + .inspectF { + case Left(10) => invokeOther() + case Left(20) => invokeOther() + case Left(0) => invoke() + } + .run + .value + + invoked && !invokedOther + } + + def simplyNotInvoke: Boolean = { + var invoked: Boolean = false + var invokedOther: Boolean = false + + def invoke() = + Eval.later { invoked = true } + + def invokeOther() = + Eval.later { invokedOther = true } + + 30.seal[Eval] + .inspectF { + case Left(10) => invokeOther() + case Left(20) => invokeOther() + case Left(0) => invoke() + } + .run + .value + + !invoked && !invokedOther + } + + def invokeOnValueOrOnNone: Boolean = { + var invoked: Boolean = false + var invokedOther: Boolean = false + + def invoke() = + Eval.later { invoked = true } + + def invokeOther() = + Eval.later { + invokedOther = true + } + val m: Eval[Option[Int]] = Eval.later(None) + + m.valueOr(0) + .inspectF { + case Left(10) => invokeOther() + case Left(20) => invokeOther() + case Left(0) => invoke() + } + .run + .value + + invoked && !invokedOther + } + + def notInvokeOnValueOrOnNone: Boolean = { + var invoked: Boolean = false + var invokedOther: Boolean = false + + def invoke() = + Eval.later { + invoked = true + } + + def invokeOther() = + Eval.later { + invokedOther = true + } + + val m: Eval[Option[Int]] = Eval.later(None) + + m.valueOr(30) + .inspectF { + case Left(10) => invokeOther() + case Left(20) => invokeOther() + case Left(0) => invoke() + } + .run + .value + !invoked && !invokedOther + } + + def invokeOnValueOrOnSome: Boolean = { + var invoked: Boolean = false + var invokedOther: Boolean = false + + def invoke() = + Eval.later { + invoked = true + } + + def invokeOther() = + Eval.later { + invokedOther = true + } + + val m: Eval[Option[Int]] = Eval.later(Some(0)) + + m.valueOr(30) + .inspectF { + case Left(10) => invokeOther() + case Left(20) => invokeOther() + case Left(30) => invokeOther() + case Right(0) => invoke() + } + .run + .value + + invoked && !invokedOther + } + + def notInvokeOnValueOrOnSome: Boolean = { + var invoked: Boolean = false + var invokedOther: Boolean = false + + def invoke() = + Eval.later { + invoked = true + } + + def invokeOther() = + Eval.later { + invokedOther = true + } + + val m: Eval[Option[Int]] = Eval.later(Some(0)) + + m.valueOr(30) + .inspectF { + case Left(10) => invokeOther() + case Left(20) => invokeOther() + case Left(30) => invoke() + } + .run + .value + + !invoked && !invokedOther + } + + def invokeOnPositiveEnsure: Boolean = { + var invoked: Boolean = false + var invokedOther: Boolean = false + + def invoke() = + Eval.later { + invoked = true + } + + def invokeOther() = + Eval.later { + invokedOther = true + } + + 0.liftSealed[Eval, Nothing] + .ensure(_ => true, 30) + .inspectF { + case Left(10) => invokeOther() + case Left(20) => invokeOther() + case Left(30) => invokeOther() + case Right(0) => invoke() + } + .run + .value + + invoked && !invokedOther + } + + def notInvokeOnPositiveEnsure: Boolean = { + var invoked: Boolean = false + var invokedOther: Boolean = false + + def invoke() = + Eval.later { + invoked = true + } + + def invokeOther() = + Eval.later { + invokedOther = true + } + + 0.liftSealed[Eval, Nothing] + .ensure(_ => true, 30) + .inspectF { + case Left(10) => invokeOther() + case Left(20) => invokeOther() + case Left(30) => invoke() + } + .run + .value + + !invoked && !invokedOther + } + + def invokeOnNegativeEnsure: Boolean = { + var invoked: Boolean = false + var invokedOther: Boolean = false + + def invoke() = + Eval.later { + invoked = true + } + + def invokeOther() = + Eval.later { + invokedOther = true + } + + 10.liftSealed[Eval, Nothing] + .ensure(_ => false, 40) + .inspectF { + case Left(10) => invokeOther() + case Left(20) => invokeOther() + case Left(30) => invokeOther() + case Left(40) => invoke() + } + .run + .value + + invoked && !invokedOther + } + + def notInvokeOnNegativeEnsure: Boolean = { + var invoked: Boolean = false + var invokedOther: Boolean = false + + def invoke() = + Eval.later { invoked = true } + + def invokeOther() = + Eval.later { + invokedOther = true + } + + 0.liftSealed[Eval, Nothing] + .ensure(_ => false, 30) + .inspectF { + case Left(10) => invokeOther() + case Left(20) => invokeOther() + case Left(30) => invokeOther() + case Left(0) => invoke() + } + .run + .value + + !invoked && invokedOther + } + + def invokeOnSomeValueOrWithNegativeEnsure: Boolean = { + var shouldBeInvoked: Boolean = false + var shouldNotBeInvoked: Boolean = false + + def invoke() = + Eval.later { + shouldBeInvoked = true + } + + def invokeOther() = + Eval.later { + shouldNotBeInvoked = true + } + + val m: Eval[Option[Int]] = Eval.later(Some(0)) + + m.valueOr(10) + .ensure(_ => false, 20) + .inspectF { + case Left(0) => invokeOther() + case Left(10) => invokeOther() + case Left(20) => invoke() + } + .run + .value + + shouldBeInvoked && !shouldNotBeInvoked + } + + def notInvokeOnSomeValueOrWithNegativeEnsure: Boolean = { + var shouldBeInvoked: Boolean = false + var shouldNotBeInvoked: Boolean = false + + def invoke() = + Eval.later { shouldBeInvoked = true } + + def invokeOther() = + Eval.later { shouldNotBeInvoked = true } + + val m: Eval[Option[Int]] = Eval.later(Some(0)) + + m.valueOr(10) + .ensure(_ => false, 80) + .inspectF { + case Left(0) => invokeOther() + case Left(10) => invokeOther() + case Left(20) => invoke() + } + .run + .value + + !shouldBeInvoked && !shouldNotBeInvoked + } +} + +object SealedSideEffectsInspectF extends SealedSideEffectsInspectF diff --git a/src/test/scala/pl/iterators/sealedmonad/side_effects/SealedSideEffectsSemiflatTap.scala b/src/test/scala/pl/iterators/sealedmonad/side_effects/SealedSideEffectsSemiflatTap.scala index abb1bc9..0af9ee0 100644 --- a/src/test/scala/pl/iterators/sealedmonad/side_effects/SealedSideEffectsSemiflatTap.scala +++ b/src/test/scala/pl/iterators/sealedmonad/side_effects/SealedSideEffectsSemiflatTap.scala @@ -55,7 +55,7 @@ trait SealedSideEffectsSemiflatTap { def invoke() = shouldBeInvoked = true - val m: Id[Option[Int]] = None + val m: Id[Option[Int]] = Id(Some(10)) m.valueOr(30) .ensure(_ => true, 30) @@ -140,7 +140,7 @@ trait SealedSideEffectsSemiflatTap { def invokeRight(): Id[Unit] = shouldBeInvoked = 2 - val m: Id[Option[Int]] = None + val m: Id[Option[Int]] = Id(Some(10)) m.valueOr(30) .ensure(_ => true, 30)