Skip to content

Commit

Permalink
restrict traverse_ and friends to require Unit
Browse files Browse the repository at this point in the history
When using `traverse_` or `sequence_` to evaluate some applicative
effect `G[B]` within the context of a foldable structure, any `B` value
is thrown away. Per the scaladoc for `traverse_`, these functions expect
that the `G[B]` is primarily a side effect or action.  The scala
convention for expressing this expectation is to require a `Unit`
parameter.

Teach `traverse_`, `sequence_`, and their parallel and nonempty
analogues to follow this convention by accepting only `G[Unit]` instead
of simply any `G[B]`.
  • Loading branch information
jpassaro committed Nov 20, 2022
1 parent a982d33 commit 128f801
Show file tree
Hide file tree
Showing 19 changed files with 79 additions and 89 deletions.
37 changes: 16 additions & 21 deletions core/src/main/scala/cats/Foldable.scala
Expand Up @@ -569,50 +569,45 @@ trait Foldable[F[_]] extends UnorderedFoldable[F] with FoldableNFunctions[F] { s
/**
* Traverse `F[A]` using `Applicative[G]`.
*
* `A` values will be mapped into `G[B]` and combined using
* `A` values will be mapped into `G[Unit]` and combined using
* `Applicative#map2`.
*
* For example:
*
* {{{
* scala> import cats.implicits._
* scala> def parseInt(s: String): Option[Int] = Either.catchOnly[NumberFormatException](s.toInt).toOption
* scala> def checkInt(s: String): Option[Unit] = Either.catchOnly[NumberFormatException]{ s.toInt ; () }.toOption
* scala> val F = Foldable[List]
* scala> F.traverse_(List("333", "444"))(parseInt)
* scala> F.traverse_(List("333", "444"))(checkInt)
* res0: Option[Unit] = Some(())
* scala> F.traverse_(List("333", "zzz"))(parseInt)
* scala> F.traverse_(List("333", "zzz"))(checkInt)
* res1: Option[Unit] = None
* }}}
*
* This method is primarily useful when `G[_]` represents an action
* or effect, and the specific `A` aspect of `G[A]` is not otherwise
* needed.
* or effect. It is equivalent to `foldMapA` using the Unit monoid.
*/
def traverse_[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: Applicative[G]): G[Unit] =
foldRight(fa, Always(G.pure(()))) { (a, acc) =>
G.map2Eval(f(a), acc) { (_, _) =>
()
}
}.value
def traverse_[G[_], A](fa: F[A])(f: A => G[Unit])(implicit G: Applicative[G]): G[Unit] =
foldMapA(fa)(f)

/**
* Sequence `F[G[A]]` using `Applicative[G]`.
* Sequence `F[G[Unit]]` using `Applicative[G]`.
*
* This is similar to `traverse_` except it operates on `F[G[A]]`
* This is similar to `traverse_` except it operates on `F[G[Unit]]`
* values, so no additional functions are needed.
*
* For example:
*
* {{{
* scala> import cats.implicits._
* scala> val F = Foldable[List]
* scala> F.sequence_(List(Option(1), Option(2), Option(3)))
* scala> F.sequence_(List(Option(()), Option(()), Option(())))
* res0: Option[Unit] = Some(())
* scala> F.sequence_(List(Option(1), None, Option(3)))
* scala> F.sequence_(List(Option(()), None, Option(())))
* res1: Option[Unit] = None
* }}}
*/
def sequence_[G[_]: Applicative, A](fga: F[G[A]]): G[Unit] =
def sequence_[G[_]: Applicative](fga: F[G[Unit]]): G[Unit] =
traverse_(fga)(identity)

/**
Expand Down Expand Up @@ -1051,10 +1046,10 @@ object Foldable {
typeClassInstance.foldMapM[G, A, B](self)(f)(G, B)
def foldMapA[G[_], B](f: A => G[B])(implicit G: Applicative[G], B: Monoid[B]): G[B] =
typeClassInstance.foldMapA[G, A, B](self)(f)(G, B)
def traverse_[G[_], B](f: A => G[B])(implicit G: Applicative[G]): G[Unit] =
typeClassInstance.traverse_[G, A, B](self)(f)(G)
def sequence_[G[_], B](implicit ev$1: A <:< G[B], ev$2: Applicative[G]): G[Unit] =
typeClassInstance.sequence_[G, B](self.asInstanceOf[F[G[B]]])
def traverse_[G[_]](f: A => G[Unit])(implicit G: Applicative[G]): G[Unit] =
typeClassInstance.traverse_[G, A](self)(f)(G)
def sequence_[G[_]](implicit ev$1: A <:< G[Unit], ev$2: Applicative[G]): G[Unit] =
typeClassInstance.sequence_[G](self.asInstanceOf[F[G[Unit]]])
def foldK[G[_], B](implicit ev$1: A <:< G[B], G: MonoidK[G]): G[B] =
typeClassInstance.foldK[G, B](self.asInstanceOf[F[G[B]]])(G)
def find(f: A => Boolean): Option[A] = typeClassInstance.find[A](self)(f)
Expand Down
14 changes: 7 additions & 7 deletions core/src/main/scala/cats/Parallel.scala
Expand Up @@ -263,7 +263,7 @@ object Parallel extends ParallelArityFunctions2 {
* Like `Foldable[A].sequence_`, but uses the applicative instance
* corresponding to the Parallel instance instead.
*/
def parSequence_[T[_]: Foldable, M[_], A](tma: T[M[A]])(implicit P: Parallel[M]): M[Unit] = {
def parSequence_[T[_]: Foldable, M[_]](tma: T[M[Unit]])(implicit P: Parallel[M]): M[Unit] = {
val fu: P.F[Unit] = Foldable[T].traverse_(tma)(P.parallel.apply(_))(P.applicative)
P.sequential(fu)
}
Expand All @@ -272,9 +272,9 @@ object Parallel extends ParallelArityFunctions2 {
* Like `Foldable[A].traverse_`, but uses the applicative instance
* corresponding to the Parallel instance instead.
*/
def parTraverse_[T[_]: Foldable, M[_], A, B](
def parTraverse_[T[_]: Foldable, M[_], A](
ta: T[A]
)(f: A => M[B])(implicit P: Parallel[M]): M[Unit] = {
)(f: A => M[Unit])(implicit P: Parallel[M]): M[Unit] = {
val gtb: P.F[Unit] = Foldable[T].traverse_(ta)(a => P.parallel(f(a)))(P.applicative)
P.sequential(gtb)
}
Expand Down Expand Up @@ -348,8 +348,8 @@ object Parallel extends ParallelArityFunctions2 {
* Like `Reducible[A].nonEmptySequence_`, but uses the apply instance
* corresponding to the Parallel instance instead.
*/
def parNonEmptySequence_[T[_]: Reducible, M[_], A](
tma: T[M[A]]
def parNonEmptySequence_[T[_]: Reducible, M[_]](
tma: T[M[Unit]]
)(implicit P: NonEmptyParallel[M]): M[Unit] = {
val fu: P.F[Unit] = Reducible[T].nonEmptyTraverse_(tma)(P.parallel.apply(_))(P.apply)
P.sequential(fu)
Expand All @@ -359,9 +359,9 @@ object Parallel extends ParallelArityFunctions2 {
* Like `Reducible[A].nonEmptyTraverse_`, but uses the apply instance
* corresponding to the Parallel instance instead.
*/
def parNonEmptyTraverse_[T[_]: Reducible, M[_], A, B](
def parNonEmptyTraverse_[T[_]: Reducible, M[_], A](
ta: T[A]
)(f: A => M[B])(implicit P: NonEmptyParallel[M]): M[Unit] = {
)(f: A => M[Unit])(implicit P: NonEmptyParallel[M]): M[Unit] = {
val gtb: P.F[Unit] = Reducible[T].nonEmptyTraverse_(ta)(a => P.parallel(f(a)))(P.apply)
P.sequential(gtb)
}
Expand Down
18 changes: 8 additions & 10 deletions core/src/main/scala/cats/Reducible.scala
Expand Up @@ -200,7 +200,7 @@ trait Reducible[F[_]] extends Foldable[F] { self =>
* Traverse `F[A]` using `Apply[G]`.
*
* `A` values will be mapped into `G[B]` and combined using
* `Apply#map2`.
* `Apply#map2`. This is equivalent to `reduceMapA`.
*
* This method is similar to [[Foldable.traverse_]]. There are two
* main differences:
Expand All @@ -213,10 +213,8 @@ trait Reducible[F[_]] extends Foldable[F] { self =>
* available for `G` and want to take advantage of short-circuiting
* the traversal.
*/
def nonEmptyTraverse_[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: Apply[G]): G[Unit] = {
val f1 = f.andThen(G.void)
reduceRightTo(fa)(f1)((x, y) => G.map2Eval(f1(x), y)((_, b) => b)).value
}
def nonEmptyTraverse_[G[_], A](fa: F[A])(f: A => G[Unit])(implicit G: Apply[G]): G[Unit] =
reduceMapA(fa)(f)

/**
* Sequence `F[G[A]]` using `Apply[G]`.
Expand All @@ -225,7 +223,7 @@ trait Reducible[F[_]] extends Foldable[F] { self =>
* an [[Apply]] instance for `G` instead of [[Applicative]]. See the
* [[nonEmptyTraverse_]] documentation for a description of the differences.
*/
def nonEmptySequence_[G[_], A](fga: F[G[A]])(implicit G: Apply[G]): G[Unit] =
def nonEmptySequence_[G[_]](fga: F[G[Unit]])(implicit G: Apply[G]): G[Unit] =
nonEmptyTraverse_(fga)(identity)

def toNonEmptyList[A](fa: F[A]): NonEmptyList[A] =
Expand Down Expand Up @@ -399,10 +397,10 @@ object Reducible {
typeClassInstance.reduceMapM[G, A, B](self)(f)(G, B)
def reduceRightTo[B](f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] =
typeClassInstance.reduceRightTo[A, B](self)(f)(g)
def nonEmptyTraverse_[G[_], B](f: A => G[B])(implicit G: Apply[G]): G[Unit] =
typeClassInstance.nonEmptyTraverse_[G, A, B](self)(f)(G)
def nonEmptySequence_[G[_], B](implicit ev$1: A <:< G[B], G: Apply[G]): G[Unit] =
typeClassInstance.nonEmptySequence_[G, B](self.asInstanceOf[F[G[B]]])(G)
def nonEmptyTraverse_[G[_]](f: A => G[Unit])(implicit G: Apply[G]): G[Unit] =
typeClassInstance.nonEmptyTraverse_[G, A](self)(f)(G)
def nonEmptySequence_[G[_]](implicit ev$1: A <:< G[Unit], G: Apply[G]): G[Unit] =
typeClassInstance.nonEmptySequence_[G](self.asInstanceOf[F[G[Unit]]])(G)
def toNonEmptyList: NonEmptyList[A] = typeClassInstance.toNonEmptyList[A](self)
def minimum(implicit A: Order[A]): A = typeClassInstance.minimum[A](self)(A)
def maximum(implicit A: Order[A]): A = typeClassInstance.maximum[A](self)(A)
Expand Down
7 changes: 2 additions & 5 deletions core/src/main/scala/cats/instances/list.scala
Expand Up @@ -129,7 +129,7 @@ trait ListInstances extends cats.kernel.instances.ListInstances {
/**
* This avoids making a very deep stack by building a tree instead
*/
override def traverse_[G[_], A, B](fa: List[A])(f: A => G[B])(implicit G: Applicative[G]): G[Unit] = {
override def traverse_[G[_], A](fa: List[A])(f: A => G[Unit])(implicit G: Applicative[G]): G[Unit] = {
// the cost of this is O(size log size)
// c(n) = n + 2 * c(n/2) = n + 2(n/2 log (n/2)) = n + n (logn - 1) = n log n
// invariant: size >= 1
Expand All @@ -155,10 +155,7 @@ trait ListInstances extends cats.kernel.instances.ListInstances {
// failed. We do not use laziness to avoid
// traversing fa, which we will do fully
// in all cases.
Eval.always {
val gb = f(a)
G.void(gb)
}
Eval.always { f(a) }
}

val len = fa.length
Expand Down
7 changes: 2 additions & 5 deletions core/src/main/scala/cats/instances/vector.scala
Expand Up @@ -137,7 +137,7 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances {
/**
* This avoids making a very deep stack by building a tree instead
*/
override def traverse_[G[_], A, B](fa: Vector[A])(f: A => G[B])(implicit G: Applicative[G]): G[Unit] = {
override def traverse_[G[_], A](fa: Vector[A])(f: A => G[Unit])(implicit G: Applicative[G]): G[Unit] = {
// the cost of this is O(size)
// c(n) = 1 + 2 * c(n/2)
// invariant: size >= 1
Expand All @@ -161,10 +161,7 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances {
// failed. We do not use laziness to avoid
// traversing fa, which we will do fully
// in all cases.
Eval.always {
val gb = f(a)
G.void(gb)
}
Eval.always { f(a) }
}

val len = fa.length
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/scala/cats/syntax/foldable.scala
Expand Up @@ -47,7 +47,8 @@ private[syntax] trait FoldableSyntaxBinCompat1 {
}

final class NestedFoldableOps[F[_], G[_], A](private val fga: F[G[A]]) extends AnyVal {
def sequence_(implicit F: Foldable[F], G: Applicative[G]): G[Unit] = F.sequence_(fga)
def sequence_(implicit F: Foldable[F], G: Applicative[G], ev: A <:< Unit): G[Unit] =
F.sequence_(fga.asInstanceOf[F[G[Unit]]])

/**
* @see [[Foldable.foldK]].
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/scala/cats/syntax/parallel.scala
Expand Up @@ -111,7 +111,7 @@ trait ParallelTraverseSyntax {
implicit final def catsSyntaxParallelTraverse_[T[_]: Foldable, A](ta: T[A]): ParallelTraversable_Ops[T, A] =
new ParallelTraversable_Ops(ta)

implicit final def catsSyntaxParallelSequence_[T[_]: Foldable, M[_], A](tma: T[M[A]]): ParallelSequence_Ops[T, M, A] =
implicit final def catsSyntaxParallelSequence_[T[_]: Foldable, M[_]](tma: T[M[Unit]]): ParallelSequence_Ops[T, M] =
new ParallelSequence_Ops(tma)
}

Expand Down Expand Up @@ -189,7 +189,7 @@ final class ParallelSequenceFilterOps[T[_], M[_], A](private val tmoa: T[M[Optio
}

final class ParallelTraversable_Ops[T[_], A](private val ta: T[A]) extends AnyVal {
def parTraverse_[M[_], B](f: A => M[B])(implicit T: Foldable[T], P: Parallel[M]): M[Unit] =
def parTraverse_[M[_]](f: A => M[Unit])(implicit T: Foldable[T], P: Parallel[M]): M[Unit] =
Parallel.parTraverse_(ta)(f)
}

Expand Down Expand Up @@ -217,7 +217,7 @@ final class ParallelSequenceOps1[T[_], M[_], A](private val tma: T[M[A]]) extend
Parallel.parSequence(tma)
}

final class ParallelSequence_Ops[T[_], M[_], A](private val tma: T[M[A]]) extends AnyVal {
final class ParallelSequence_Ops[T[_], M[_]](private val tma: T[M[Unit]]) extends AnyVal {
def parSequence_(implicit T: Foldable[T], P: Parallel[M]): M[Unit] =
Parallel.parSequence_(tma)
}
Expand Down
4 changes: 2 additions & 2 deletions docs/nomenclature.md
Expand Up @@ -115,8 +115,8 @@ Like the previous section, we use the `E` for the error parameter type.
| `F[A] => (A => G[B]) => G[B]` | `foldMapM` | `G: Monad` and `B: Monoid`
| `F[A] => (A => B) => Option[B]` | `collectFirst` | The `A => B` is a `PartialFunction`
| `F[A] => (A => Option[B]) => Option[B]` | `collectFirstSome` |
| `F[A] => (A => G[B]) => G[Unit]` | `traverse_` | `G: Applicative`
| `F[G[A]] => G[Unit]` | `sequence_` | `G: Applicative`
| `F[A] => (A => G[Unit]) => G[Unit]` | `traverse_` | `G: Applicative`
| `F[G[Unit]] => G[Unit]` | `sequence_` | `G: Applicative`
| `F[A] => (A => Either[B, C] => (F[B], F[C])` | `partitionEither` | `G: Applicative`

### Reducible
Expand Down
6 changes: 3 additions & 3 deletions docs/typeclasses/foldable.md
Expand Up @@ -52,10 +52,10 @@ Foldable[List].nonEmpty(List(1,2))
Foldable[Option].toList(Option(1))
Foldable[Option].toList(None)

def parseInt(s: String): Option[Int] = scala.util.Try(Integer.parseInt(s)).toOption
def checkInt(s: String): Option[Unit] = scala.util.Try{ Integer.parseInt(s) ; () }.toOption

Foldable[List].traverse_(List("1", "2"))(parseInt)
Foldable[List].traverse_(List("1", "A"))(parseInt)
Foldable[List].traverse_(List("1", "2"))(checkInt)
Foldable[List].traverse_(List("1", "A"))(checkInt)
Foldable[List].sequence_(List(Option(1), Option(2)))
Foldable[List].sequence_(List(Option(1), None))

Expand Down
2 changes: 1 addition & 1 deletion docs/typeclasses/reducible.md
Expand Up @@ -38,7 +38,7 @@ Reducible[NonEmptyList].reduceLeftTo(NonEmptyList.of(1,2,3,4))(_.toString)((s,i)
Reducible[NonEmptyList].reduceRightTo(NonEmptyList.of(1,2,3,4))(_.toString)((i,s) => Later(s.value + i)).value
Reducible[NonEmptyList].nonEmptyIntercalate(NonEmptyList.of("a", "b", "c"), ", ")

def countChars(s: String) = s.toCharArray.groupBy(identity).view.mapValues(_.length).toMap
def countChars(s: String) = s.toCharArray.groupBy(identity).view.mapValues(x => { x.length ; () }).toMap

Reducible[NonEmptyList].nonEmptyTraverse_(NonEmptyList.of("Hello", "World"))(countChars)
Reducible[NonEmptyVector].nonEmptyTraverse_(NonEmptyVector.of("Hello", ""))(countChars)
Expand Down
4 changes: 2 additions & 2 deletions laws/src/main/scala/cats/laws/ReducibleLaws.scala
Expand Up @@ -53,10 +53,10 @@ trait ReducibleLaws[F[_]] extends FoldableLaws[F] {
def reduceReduceLeftConsistent[B](fa: F[B])(implicit B: Semigroup[B]): IsEq[B] =
fa.reduce <-> fa.reduceLeft(B.combine)

def traverseConsistent[G[_]: Applicative, A, B](fa: F[A], f: A => G[B]): IsEq[G[Unit]] =
def traverseConsistent[G[_]: Applicative, A](fa: F[A], f: A => G[Unit]): IsEq[G[Unit]] =
fa.nonEmptyTraverse_(f) <-> fa.traverse_(f)

def sequenceConsistent[G[_]: Applicative, A](fa: F[G[A]]): IsEq[G[Unit]] =
def sequenceConsistent[G[_]: Applicative](fa: F[G[Unit]]): IsEq[G[Unit]] =
fa.nonEmptySequence_ <-> fa.sequence_

def sizeConsistent[A](fa: F[A]): IsEq[Long] =
Expand Down
Expand Up @@ -44,13 +44,11 @@ trait NonEmptyTraverseTests[F[_]] extends TraverseTests[F] with ReducibleTests[F
ArbYB: Arbitrary[Y[B]],
ArbYC: Arbitrary[Y[C]],
ArbFB: Arbitrary[F[B]],
ArbFM: Arbitrary[F[M]],
ArbXM: Arbitrary[X[M]],
ArbYM: Arbitrary[Y[M]],
ArbFGA: Arbitrary[F[G[A]]],
ArbGU: Arbitrary[G[Unit]],
ArbFGU: Arbitrary[F[G[Unit]]],
ArbFXM: Arbitrary[F[X[M]]],
ArbGB: Arbitrary[G[B]],
ArbGM: Arbitrary[G[M]],
CogenA: Cogen[A],
CogenB: Cogen[B],
CogenC: Cogen[C],
Expand Down
8 changes: 4 additions & 4 deletions laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala
Expand Up @@ -35,8 +35,8 @@ trait ReducibleTests[F[_]] extends FoldableTests[F] {
def reducible[G[_]: Applicative, A: Arbitrary, B: Arbitrary](implicit
ArbFA: Arbitrary[F[A]],
ArbFB: Arbitrary[F[B]],
ArbFGA: Arbitrary[F[G[A]]],
ArbGB: Arbitrary[G[B]],
ArbFGU: Arbitrary[F[G[Unit]]],
ArbGU: Arbitrary[G[Unit]],
CogenA: Cogen[A],
CogenB: Cogen[B],
EqG: Eq[G[Unit]],
Expand All @@ -58,8 +58,8 @@ trait ReducibleTests[F[_]] extends FoldableTests[F] {
forAll(laws.reduceRightConsistentWithReduceRightOption[A] _),
"reduce consistent with reduceLeft" ->
forAll(laws.reduceReduceLeftConsistent[B] _),
"nonEmptyTraverse_ consistent with traverse_" -> forAll(laws.traverseConsistent[G, A, B] _),
"nonEmptySequence_ consistent with sequence_" -> forAll(laws.sequenceConsistent[G, A] _),
"nonEmptyTraverse_ consistent with traverse_" -> forAll(laws.traverseConsistent[G, A] _),
"nonEmptySequence_ consistent with sequence_" -> forAll(laws.sequenceConsistent[G] _),
"size consistent with reduceMap" -> forAll(laws.sizeConsistent[A] _)
)
}
Expand Down
Expand Up @@ -179,7 +179,7 @@ trait ScalaVersionSpecificRegressionSuite { self: RegressionSuite =>
// shouldn't have ever evaluated validate(8)
checkAndResetCount(3)

assert(LazyList(1, 2, 6, 8).traverse_(validate) === (Either.left("6 is greater than 5")))
assert(LazyList(1, 2, 6, 8).traverse_(validate(_).void) === (Either.left("6 is greater than 5")))
checkAndResetCount(3)
}
}
Expand Down
18 changes: 9 additions & 9 deletions tests/shared/src/test/scala/cats/tests/ParallelSuite.scala
Expand Up @@ -79,23 +79,23 @@ class ParallelSuite
}

test("ParTraverse_ identity should be equivalent to parSequence_") {
forAll { (es: SortedSet[Either[String, Int]]) =>
assert(Parallel.parTraverse_(es)(identity) === (Parallel.parSequence_[SortedSet, Either[String, *], Int](es)))
forAll { (es: SortedSet[Either[String, Unit]]) =>
assert(Parallel.parTraverse_(es)(identity) === (Parallel.parSequence_[SortedSet, Either[String, *]](es)))
}
}

test("ParTraverse_ syntax should be equivalent to Parallel.parTraverse_") {
forAll { (es: SortedSet[Either[String, Int]]) =>
forAll { (es: SortedSet[Either[String, Unit]]) =>
assert(
Parallel.parTraverse_[SortedSet, Either[String, *], Either[String, Int], Int](es)(identity) === (es
Parallel.parTraverse_[SortedSet, Either[String, *], Either[String, Unit]](es)(identity) === (es
.parTraverse_(identity))
)
}
}

test("ParSequence_ syntax should be equivalent to Parallel.parSequence_") {
forAll { (es: SortedSet[Either[String, Int]]) =>
assert(Parallel.parSequence_[SortedSet, Either[String, *], Int](es) === (es.parSequence_))
forAll { (es: SortedSet[Either[String, Unit]]) =>
assert(Parallel.parSequence_[SortedSet, Either[String, *]](es) === (es.parSequence_))
}
}

Expand All @@ -106,7 +106,7 @@ class ParallelSuite
}

test("ParNonEmptyTraverse_ identity should be equivalent to parNonEmptySequence_") {
forAll { (es: NonEmptyList[Either[String, Int]]) =>
forAll { (es: NonEmptyList[Either[String, Unit]]) =>
assert(Parallel.parNonEmptyTraverse_(es)(identity) === (Parallel.parNonEmptySequence_(es)))
}
}
Expand Down Expand Up @@ -311,8 +311,8 @@ class ParallelSuite
}

test("ParReplicateA_ should be equivalent to fill parSequence_") {
forAll(Gen.choose(1, 20), Arbitrary.arbitrary[Either[String, String]]) {
(repetitions: Int, e: Either[String, String]) =>
forAll(Gen.choose(1, 20), Arbitrary.arbitrary[Either[String, Unit]]) {
(repetitions: Int, e: Either[String, Unit]) =>
assert(Parallel.parReplicateA_(repetitions, e) === Parallel.parSequence_(List.fill(repetitions)(e)))
}
}
Expand Down
Expand Up @@ -238,7 +238,7 @@ abstract class ReducibleSuite[F[_]: Reducible](name: String)(implicit
val notAllEven = fromValues(2, 4, 6, 9, 10, 12, 14)
val out = mutable.ListBuffer[Int]()

notAllEven.nonEmptyTraverse_ { a => out += a; if (a % 2 == 0) Some(a) else None }
notAllEven.nonEmptyTraverse_ { a => out += a; if (a % 2 == 0) Some(()) else None }

assert(out.toList === List(2, 4, 6, 9))
}
Expand Down

0 comments on commit 128f801

Please sign in to comment.