Skip to content

Commit

Permalink
fix: make the NaturalTransformation lazy in order to not duplicate ef…
Browse files Browse the repository at this point in the history
…fects

for Id ~> Action
  • Loading branch information
etorreborre committed Oct 12, 2024
1 parent 3383b3a commit 846d0a4
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 6 deletions.
8 changes: 7 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,13 @@ lazy val mimaSettings =
// Because, in that case, it is very possible to confuse the ownEnv with the env and shutdown the env
// which breaks the execution of the whole specification
ProblemFilters.exclude[DirectMissingMethodProblem]("org.specs2.specification.core.OwnEnv.ownEnv"),
ProblemFilters.exclude[DirectMissingMethodProblem]("org.specs2.specification.core.OwnExecutionEnv.env")
ProblemFilters.exclude[DirectMissingMethodProblem]("org.specs2.specification.core.OwnExecutionEnv.env"),

// issue #1277 NaturalTransformation needs to be made lazier
ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.specs2.fp.NaturalTransformation.apply"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("org.specs2.fp.NaturalTransformation.apply"),
ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.specs2.fp.NaturalTransformation#naturalId.apply"),
ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.specs2.control.Operation#operationToAction.apply")
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ object Operation:
"Applicative[Operation]"

given operationToAction: NaturalTransformation[Operation, Action] with
def apply[A](operation: Operation[A]): Action[A] =
def apply[A](operation: =>Operation[A]): Action[A] =
operation.toAction

given SafeOperation: Safe[Operation] with
Expand Down
27 changes: 27 additions & 0 deletions core/jvm/src/test/scala/org/specs2/fp/FoldSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package user

import org.specs2.*
import org.specs2.concurrent.*
import org.specs2.control.*
import org.specs2.control.origami.*
import org.specs2.control.producer.*
import org.specs2.fp.*

class FoldSpec(ee: ExecutionEnv) extends Specification {
def is = s2"""

A correct implementation of NaturalTransformation from Id to Action must not duplicate effects (see issue #1277) $dontDuplicateEffects

"""

def dontDuplicateEffects = {
val p = Producer.emitAll[Action, Int](1, 2, 3)

// The NaturalTransformation from Id (which is Id[X] X) to Action must
// make sure to not run side effects of X twice.
// This tests guarantees that during the evaluation of `zip` and the various folds
// we don't run the action for collection elements into a mutable map more than necessary.
val vs = p.fold(Folds.list[Int].into[Action] `zip` Folds.list[Int].into[Action])
vs.run(ee) === (List(1, 2, 3), List(1, 2, 3))
}
}
4 changes: 2 additions & 2 deletions fp/src/main/scala/org/specs2/fp/NaturalTransformation.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package org.specs2.fp

trait NaturalTransformation[-F[_], +G[_]]:
def apply[A](fa: F[A]): G[A]
def apply[A](fa: =>F[A]): G[A]

object NaturalTransformation:

given naturalId[M[_]: Monad]: NaturalTransformation[Id, M] with
def apply[A](fa: Id[A]): M[A] =
def apply[A](fa: =>Id[A]): M[A] =
summon[Monad[M]].point(fa)
5 changes: 3 additions & 2 deletions html/src/main/scala/org/specs2/reporter/HtmlPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,16 @@ case class HtmlPrinter(env: Env, searchPage: SearchPage, logger: Logger = Consol

/** @return a SinkTask for the Html output */
def sink(spec: SpecStructure): AsyncSink[Fragment] =
((Statistics.fold `zip` list[Fragment].into[Action] `zip` SimpleTimer.timerFold.into[Action]) <*
fromStart((getHtmlOptions(env.arguments) >>= (options => copyResources(env, options))).void.toAction))
val copyHtmlResources = (getHtmlOptions(env.arguments) >>= (options => copyResources(env, options))).void.toAction
val htmlSink = (Statistics.fold `zip` list[Fragment].into[Action] `zip` SimpleTimer.timerFold.into[Action])
.mapFlatten { case ((stats, fragments), timer) =>
val executedSpec = spec.copy(lazyFragments = () => Fragments(fragments*))
getPandoc(env).flatMap {
case Some(pandoc) => printHtmlWithPandoc(env, executedSpec, stats, timer, pandoc).toAction
case _ => printHtml(env, executedSpec, stats, timer).toAction
}
}
htmlSink.startWith(copyHtmlResources)

/** WITHOUT PANDOC
*/
Expand Down

0 comments on commit 846d0a4

Please sign in to comment.