Skip to content
Merged
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
42 changes: 29 additions & 13 deletions core/src/main/scala/cats/Alternative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import scala.annotation.implicitNotFound
@implicitNotFound("Could not find an instance of Alternative for ${F}")
@typeclass trait Alternative[F[_]] extends NonEmptyAlternative[F] with MonoidK[F] { self =>

// Note: `protected` is only necessary to enforce binary compatibility
// since neither `private` nor `private[cats]` work properly here.
@deprecated("use a FlatMap-constrained version instead", "2.6.2")
protected def unite[G[_], A](fga: F[G[A]])(FM: Monad[F], G: Foldable[G]): F[A] = {
implicit def FM0: FlatMap[F] = FM
implicit def G0: Foldable[G] = G
unite(fga)
}

/**
* Fold over the inner structure to combine all of the values with
* our combine method inherited from MonoidK. The result is for us
Expand All @@ -15,39 +24,44 @@ import scala.annotation.implicitNotFound
*
* Example:
* {{{
* scala> import cats.implicits._
* scala> val x: List[Vector[Int]] = List(Vector(1, 2), Vector(3, 4))
* scala> Alternative[List].unite(x)
* res0: List[Int] = List(1, 2, 3, 4)
* }}}
*/
def unite[G[_], A](fga: F[G[A]])(implicit FM: Monad[F], G: Foldable[G]): F[A] =
FM.flatMap(fga) { ga =>
G.foldLeft(ga, empty[A])((acc, a) => appendK(acc, a))
}
def unite[G[_], A](fga: F[G[A]])(implicit FM: FlatMap[F], G: Foldable[G]): F[A] =
FM.flatMap(fga) { G.foldMapK(_)(pure)(self) }
Comment on lines +32 to +33

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The original implementation is replaced with this better optimized one which utilizes Foldable.foldMapK (instead of the former's foldLeft).


// Note: `protected` is only necessary to enforce binary compatibility
// since neither `private` nor `private[cats]` work properly here.
@deprecated("use a FlatMap-constrained version instead", "2.6.2")
protected def separate[G[_, _], A, B](fgab: F[G[A, B]])(FM: Monad[F], G: Bifoldable[G]): (F[A], F[B]) = {
implicit def FM0: FlatMap[F] = FM
implicit def G0: Bifoldable[G] = G
separate(fgab)
}

/**
* Separate the inner foldable values into the "lefts" and "rights"
* Separate the inner foldable values into the "lefts" and "rights".
*
* Example:
* {{{
* scala> import cats.implicits._
* scala> val l: List[Either[String, Int]] = List(Right(1), Left("error"))
* scala> Alternative[List].separate(l)
* res0: (List[String], List[Int]) = (List(error),List(1))
* }}}
*/
def separate[G[_, _], A, B](fgab: F[G[A, B]])(implicit FM: Monad[F], G: Bifoldable[G]): (F[A], F[B]) = {
def separate[G[_, _], A, B](fgab: F[G[A, B]])(implicit FM: FlatMap[F], G: Bifoldable[G]): (F[A], F[B]) = {
val as = FM.flatMap(fgab)(gab => G.bifoldMap(gab)(pure, _ => empty[A])(algebra[A]))
val bs = FM.flatMap(fgab)(gab => G.bifoldMap(gab)(_ => empty[B], pure)(algebra[B]))
(as, bs)
}

/**
* Separate the inner foldable values into the "lefts" and "rights".
* A variant of [[separate]] that is specialized
* for Fs that have Foldable instances
* which allows for a single-pass implementation
*
* A variant of [[[separate[G[_,_],A,B](fgab:F[G[A,B]])(implicitFM:cats\.FlatMap[F]* separate]]]
* that is specialized for Fs that have Foldable instances which allows for a single-pass implementation
* (as opposed to {{{separate}}} which is 2-pass).
*
* Example:
Expand Down Expand Up @@ -115,9 +129,11 @@ object Alternative {
def self: F[A]
val typeClassInstance: TypeClassType
def unite[G[_], B](implicit ev$1: A <:< G[B], FM: Monad[F], G: Foldable[G]): F[B] =
typeClassInstance.unite[G, B](self.asInstanceOf[F[G[B]]])(FM, G)
// Note: edited manually since seems Simulacrum is not able to handle the bin-compat redirection properly.
typeClassInstance.unite[G, B](self.asInstanceOf[F[G[B]]])
def separate[G[_, _], B, C](implicit ev$1: A <:< G[B, C], FM: Monad[F], G: Bifoldable[G]): (F[B], F[C]) =
typeClassInstance.separate[G, B, C](self.asInstanceOf[F[G[B, C]]])(FM, G)
// Note: edited manually since seems Simulacrum is not able to handle the bin-compat redirection properly.
typeClassInstance.separate[G, B, C](self.asInstanceOf[F[G[B, C]]])
def separateFoldable[G[_, _], B, C](implicit ev$1: A <:< G[B, C], G: Bifoldable[G], FF: Foldable[F]): (F[B], F[C]) =
typeClassInstance.separateFoldable[G, B, C](self.asInstanceOf[F[G[B, C]]])(G, FF)
}
Expand Down
50 changes: 34 additions & 16 deletions core/src/main/scala/cats/syntax/alternative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@ trait AlternativeSyntax {
new GuardOps(b)
}

final class UniteOps[F[_], G[_], A](private val fga: F[G[A]]) extends AnyVal {
Comment thread
satorg marked this conversation as resolved.
final class UniteOps[F[_], G[_], A](protected val fga: F[G[A]]) extends AnyVal with UniteOpsBinCompat0[F, G, A] {

@deprecated("use a FlatMap-constrained version instead", "2.6.2")
protected def unite(F: Monad[F], A: Alternative[F], G: Foldable[G]): F[A] =
A.unite(fga)(F, G)
}

sealed private[syntax] trait UniteOpsBinCompat0[F[_], G[_], A] extends Any { self: UniteOps[F, G, A] =>

/**
* @see [[Alternative.unite]]
*
* See [[[Alternative.unite[G[_],A](fga:F[G[A]])(implicitFM:cats\.FlatMap[F]*]]]
*
* Example:
* {{{
* scala> import cats.implicits._
Expand All @@ -26,44 +33,55 @@ final class UniteOps[F[_], G[_], A](private val fga: F[G[A]]) extends AnyVal {
* res0: List[Int] = List(1, 2, 3, 4)
* }}}
*/
def unite(implicit F: Monad[F], A: Alternative[F], G: Foldable[G]): F[A] = A.unite[G, A](fga)
def unite(implicit F: FlatMap[F], A: Alternative[F], G: Foldable[G]): F[A] =
A.unite[G, A](fga)
}

final class SeparateOps[F[_], G[_, _], A, B](private val fgab: F[G[A, B]]) extends AnyVal {
final class SeparateOps[F[_], G[_, _], A, B](protected val fgab: F[G[A, B]])
extends AnyVal
with SeparateOpsBinCompat0[F, G, A, B] {

@deprecated("use a FlatMap-constrained version instead", "2.6.2")
protected def separate(F: Monad[F], A: Alternative[F], G: Bifoldable[G]): (F[A], F[B]) =
A.separate[G, A, B](fgab)(F, G)

/**
* @see [[Alternative.separate]]
*
* See [[Alternative.separateFoldable]]
*
* Example:
* {{{
* scala> import cats.implicits._
* scala> val l: List[Either[String, Int]] = List(Right(1), Left("error"))
* scala> l.separate
* scala> l.separateFoldable
* res0: (List[String], List[Int]) = (List(error),List(1))
* }}}
*/
def separate(implicit F: Monad[F], A: Alternative[F], G: Bifoldable[G]): (F[A], F[B]) = A.separate[G, A, B](fgab)
def separateFoldable(implicit F: Foldable[F], A: Alternative[F], G: Bifoldable[G]): (F[A], F[B]) =
A.separateFoldable[G, A, B](fgab)
}

sealed private[syntax] trait SeparateOpsBinCompat0[F[_], G[_, _], A, B] extends Any { self: SeparateOps[F, G, A, B] =>

/**
* @see [[Alternative.separateFoldable]]
*
* See [[[Alternative.separate[G[_,_],A,B](fgab:F[G[A,B]])(implicitFM:cats\.FlatMap[F]* Alternative.separate]]]
*
* Example:
* {{{
* scala> import cats.implicits._
* scala> val l: List[Either[String, Int]] = List(Right(1), Left("error"))
* scala> l.separateFoldable
* scala> l.separate
* res0: (List[String], List[Int]) = (List(error),List(1))
* }}}
*/
def separateFoldable(implicit F: Foldable[F], A: Alternative[F], G: Bifoldable[G]): (F[A], F[B]) =
A.separateFoldable[G, A, B](fgab)
def separate(implicit F: FlatMap[F], A: Alternative[F], G: Bifoldable[G]): (F[A], F[B]) =
A.separate[G, A, B](fgab)
}

final class GuardOps(private val condition: Boolean) extends AnyVal {

/**
* @see [[Alternative.guard]]
*
* See [[Alternative.guard]]
*
* Example:
* {{{
* scala> import cats.implicits._
Expand Down
21 changes: 18 additions & 3 deletions tests/src/test/scala/cats/tests/AlternativeSuite.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cats.tests

import cats.Alternative
import cats.FlatMap
import cats.laws.discipline.AlternativeTests
import cats.syntax.eq._
import org.scalacheck.Prop._
Expand All @@ -22,16 +23,30 @@ class AlternativeSuite extends CatsSuite {
val expected = list.collect { case Some(s) => s }

assert(Alternative[List].unite(list) === expected)

// See #3997: check that correct `unite` version is picked up.
implicit val listWrapperAlternative: Alternative[ListWrapper] = ListWrapper.alternative
implicit val listWrapperFlatMap: FlatMap[ListWrapper] = ListWrapper.flatMap

assert(Alternative[ListWrapper].unite(ListWrapper(list)).list === expected)
}
}

property("separate") {
forAll { (list: List[Either[Int, String]]) =>
val ints = list.collect { case Left(i) => i }
val strings = list.collect { case Right(s) => s }
val expected = (ints, strings)
val expectedInts = list.collect { case Left(i) => i }
val expectedStrings = list.collect { case Right(s) => s }
val expected = (expectedInts, expectedStrings)

assert(Alternative[List].separate(list) === expected)

// See #3997: check that correct `separate` version is picked up.
implicit val listWrapperAlternative: Alternative[ListWrapper] = ListWrapper.alternative
implicit val listWrapperFlatMap: FlatMap[ListWrapper] = ListWrapper.flatMap

val (obtainedLwInts, obtainedLwStrings) = Alternative[ListWrapper].separate(ListWrapper(list))
assert(obtainedLwInts.list === expectedInts)
assert(obtainedLwStrings.list === expectedStrings)
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/src/test/scala/cats/tests/SyntaxSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ object SyntaxSuite {
val fa2: F[A] = fa.appendK(a)
}

def testAlternativeMonad[F[_]: Alternative: Monad, G[_]: Foldable, H[_, _]: Bifoldable, A, B]: Unit = {
def testAlternativeFlatMap[F[_]: Alternative: FlatMap, G[_]: Foldable, H[_, _]: Bifoldable, A, B]: Unit = {
val fga = mock[F[G[A]]]
val fa = fga.unite

Expand Down