Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion binCompatTest/src/main/scala-2/catsBC/MimaExceptions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ object MimaExceptions {
(
cats.Bimonad[cats.data.NonEmptyChain],
cats.NonEmptyTraverse[cats.data.NonEmptyChain],
cats.SemigroupK[cats.data.NonEmptyChain]
cats.SemigroupK[cats.data.NonEmptyChain],
List("foo", "bar", "baz").traverse_(x => Either.right(x))
)
)
}
31 changes: 15 additions & 16 deletions core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -569,50 +569,49 @@ 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] =
def traverse_[G[_], A](fa: F[A])(f: A => G[Unit])(implicit G: Applicative[G]): G[Unit] =
foldRight(fa, Always(G.pure(()))) { (a, acc) =>
G.map2Eval(f(a), acc) { (_, _) =>
()
}
}.value

/**
* 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 +1050,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
Original file line number Diff line number Diff line change
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
14 changes: 7 additions & 7 deletions core/src/main/scala/cats/Reducible.scala
Original file line number Diff line number Diff line change
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,7 +213,7 @@ 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] = {
def nonEmptyTraverse_[G[_], A](fa: F[A])(f: A => G[Unit])(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
}
Expand All @@ -225,7 +225,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 +399,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
6 changes: 3 additions & 3 deletions core/src/main/scala/cats/instances/list.scala
Original file line number Diff line number Diff line change
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 @@ -156,8 +156,8 @@ trait ListInstances extends cats.kernel.instances.ListInstances {
// traversing fa, which we will do fully
// in all cases.
Eval.always {
val gb = f(a)
G.void(gb)
val fb = f(a)
G.void(fb)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need this now. fb: G[Unit] so the void is a no-op.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We absolutely do, or the change is not compatible. See #4352 (comment).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow you. My claim is that f(a): G[Unit] already. While G.void(f(a)) is still G[Unit], it isn't needed and is likely to be wasteful.

In fact, unconditionally calling void internal to a method is a code smell that should have alerted us the type was poor to begin with.

That said, there could be ergonomic reasons to not have the users required to call this void.

@armanbilge armanbilge Nov 21, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow you. My claim is that f(a): G[Unit] already

Except, it's not really :) sure, that's what the current signature claims, and a signature of G[Unit] is binary-compatible with a signature of G[A] for an unbounded type parameter A (due to type erasure).

But Cats still has to retain compatibility with calling code that was passing e.g. a G[String] there. Otherwise we will get class cast exceptions, exactly as demonstrated in #4352 (comment).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@johnynek just to spell it out: once the MIMA test was added, returning f(a) without void caused it to break.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to be pedantic :) actually, that's not a MiMa test. MiMa compares the binary-compatibility of signatures. MiMa can only detect linking errors but not other errors such as class casting exceptions. In this case there is no linking error, because due to type erasure the method signature is the same.

What I added was a runtime-compatibility test. Cats has an internal test project, that is compiled against Cats 2.0.0 but run against the latest development version of Cats. It shows that code compiled with an old version of Cats, that is not passing G[Unit] to these methods will encounter a class cast exception if we rewrite the code here to assume we are indeed getting a G[Unit].

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for the explanation!

I'd love to understand the team's thoughts and @armanbilge 's in particular on:
a) what's your evaluation of the problem? to be precise: is there value, if we can do it in a binary-compatible way, in alerting users to the code-smell of calling traverse_ and friends with non-Unit values?
b) what do you think of the approach to that problem of adding aliases with the correct signature and deprecating the original methods?

@armanbilge armanbilge Nov 21, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is value. See this issue I opened proposing a lint for flatTap, which currently suffers from a similar problem.

The linting route offers a safe way to opt-in to this change.

However, there are different perspectives. Quoting @SystemFw from the Discord discussion linked in that issue.

this discussion has happened a few times
it boils down to safety vs boilerplate, because it applies also to >> <*, etc
I wouldn't say that flatTap having Unit makes it more safe in practice, because the purpose of flatTap is to throw the B away

@SystemFw SystemFw Nov 23, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, I can make the same exact point here. The whole point of traverse_ is the avoid the boilerplate of calling void, so if I have to do list.traverse_(a => f(a).void), why don't I just do list.traverse(f).void? (And if the answer is, to avoid allocating the list, if you really care about that there is still foldMapA)

That being said, I don't feel strongly about this change :)

}
}

Expand Down
6 changes: 3 additions & 3 deletions core/src/main/scala/cats/instances/vector.scala
Original file line number Diff line number Diff line change
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 @@ -162,8 +162,8 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances {
// traversing fa, which we will do fully
// in all cases.
Eval.always {
val gb = f(a)
G.void(gb)
val fb = f(a)
G.void(fb)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment, this void is not needed.

}
}

Expand Down
3 changes: 2 additions & 1 deletion core/src/main/scala/cats/syntax/foldable.scala
Original file line number Diff line number Diff line change
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]]])
Comment on lines +50 to +51

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one is also causing binary compatibility issues, and in this case I don't see how this function can still exist in its current form (i.e. without said evidence) -- I guess it would have to move or something and that would for sure break binary compatibility? not sure if there is a fix here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, we cannot change the signature of this method. What we can do is add a new ops class:

final class NestedUnitFoldableOps[F[_], G[_]](private val fgu: F[G[Unit]])

@jpassaro jpassaro Nov 21, 2022

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this one has to change then to F.traverse_(fga)(G.void(_))?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems so.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That still allows people to call .sequence on non-unit structures -- I would suggest deprecating it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

though even that seems so knotty I'm not sure it's worthwhile.

honestly, this is my feeling about this PR as a whole. It's obvious at the moment we don't even have existing test coverage to safely make this change.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we can't change signatures, what about adding new methods with an appropriate signature and deprecating the old ones?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recall I did something similar – i.e. was tuning a method with a very subtle change in its signature while preserving its name: #3997. Not exactly the same though, but there are some similarities apparent.

if we can't change signatures, what about adding new methods with an appropriate signature and deprecating the old ones?

Personally, I don't think it is a good idea, especially because the old methods do not have such bugs that would hinder their usage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@armanbilge

It's obvious at the moment we don't even have existing test coverage to safely make this change.

Do you mean, there's no tests for traverse_ nor sequence_ or do you mean some specific test scenarios?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


/**
* @see [[Foldable.foldK]].
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/scala/cats/syntax/parallel.scala
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
10 changes: 5 additions & 5 deletions docs/typeclasses/foldable.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ 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].sequence_(List(Option(1), Option(2)))
Foldable[List].sequence_(List(Option(1), None))
Foldable[List].traverse_(List("1", "2"))(checkInt)
Foldable[List].traverse_(List("1", "A"))(checkInt)
Foldable[List].sequence_(List(Option(()), Option(())))
Foldable[List].sequence_(List(Option(()), None))

Foldable[List].forallM(List(1, 2, 3))(i => if (i < 2) Some(i % 2 == 0) else None)
Foldable[List].existsM(List(1, 2, 3))(i => if (i < 2) Some(i % 2 == 0) else None)
Expand Down
4 changes: 2 additions & 2 deletions docs/typeclasses/reducible.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ 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)
Reducible[NonEmptyList].nonEmptySequence_(NonEmptyList.of(Map(1 -> 'o'), Map(1 -> 'o')))
Reducible[NonEmptyList].nonEmptySequence_(NonEmptyList.of(Map(1 -> ()), Map(1 -> ())))

```

Expand Down
4 changes: 2 additions & 2 deletions laws/src/main/scala/cats/laws/ReducibleLaws.scala
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ trait NonEmptyTraverseTests[F[_]] extends TraverseTests[F] with ReducibleTests[F
EqXFM: Eq[X[F[M]]],
EqYFB: Eq[Y[F[B]]],
EqYFM: Eq[Y[F[M]]],
EqOptionA: Eq[Option[A]]
EqOptionA: Eq[Option[A]],
ArbGU: Arbitrary[G[Unit]],
ArbFGU: Arbitrary[F[G[Unit]]]
): RuleSet = new RuleSet {
def name: String = "nonEmptyTraverse"
def bases: Seq[(String, RuleSet)] = Nil
Expand Down
8 changes: 5 additions & 3 deletions laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ trait ReducibleTests[F[_]] extends FoldableTests[F] {
EqFA: Eq[F[A]],
EqOptionA: Eq[Option[A]],
MonoidA: CommutativeMonoid[A],
MonoidB: CommutativeMonoid[B]
MonoidB: CommutativeMonoid[B],
ArbFGU: Arbitrary[F[G[Unit]]],
ArbGU: Arbitrary[G[Unit]]
): RuleSet =
new DefaultRuleSet(
name = "reducible",
Expand All @@ -58,8 +60,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading