From 0c2c630626d3490f83927492fadf885d6d115c83 Mon Sep 17 00:00:00 2001 From: etorreborre Date: Sat, 12 Oct 2024 16:38:56 +0200 Subject: [PATCH] fix: make the NaturalTransformation lazy in order to not duplicate effects for Id ~> Action --- .../scala/org/specs2/control/Operation.scala | 2 +- .../test/scala/org/specs2/fp/FoldSpec.scala | 27 +++++++++++++++++++ .../org/specs2/fp/NaturalTransformation.scala | 4 +-- 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 core/jvm/src/test/scala/org/specs2/fp/FoldSpec.scala diff --git a/common/shared/src/main/scala/org/specs2/control/Operation.scala b/common/shared/src/main/scala/org/specs2/control/Operation.scala index 4b0d46b6bf..312f41dd6c 100644 --- a/common/shared/src/main/scala/org/specs2/control/Operation.scala +++ b/common/shared/src/main/scala/org/specs2/control/Operation.scala @@ -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 diff --git a/core/jvm/src/test/scala/org/specs2/fp/FoldSpec.scala b/core/jvm/src/test/scala/org/specs2/fp/FoldSpec.scala new file mode 100644 index 0000000000..99ebd1d711 --- /dev/null +++ b/core/jvm/src/test/scala/org/specs2/fp/FoldSpec.scala @@ -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)) + } +} diff --git a/fp/src/main/scala/org/specs2/fp/NaturalTransformation.scala b/fp/src/main/scala/org/specs2/fp/NaturalTransformation.scala index 8fa5d7f046..0a0f0713f2 100644 --- a/fp/src/main/scala/org/specs2/fp/NaturalTransformation.scala +++ b/fp/src/main/scala/org/specs2/fp/NaturalTransformation.scala @@ -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)