Skip to content

Commit

Permalink
Generalize partitionE to Reducible
Browse files Browse the repository at this point in the history
  • Loading branch information
Luka Jacobowitz committed Aug 29, 2017
1 parent 8a5ec3f commit 3be4721
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 47 deletions.
19 changes: 17 additions & 2 deletions core/src/main/scala/cats/Reducible.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cats

import cats.data.NonEmptyList

import cats.data.{Ior, NonEmptyList}
import simulacrum.typeclass

/**
Expand Down Expand Up @@ -177,6 +176,22 @@ import simulacrum.typeclass
Reducible[NonEmptyList].reduce(NonEmptyList(hd, a :: intersperseList(tl, a)))
}

def partitionE[A, B, C](fa: F[A])(f: A => Either[B, C]): Ior[NonEmptyList[B], NonEmptyList[C]] = {
import cats.syntax.either._

def g(a: A, eval: Eval[Ior[NonEmptyList[B], NonEmptyList[C]]]): Eval[Ior[NonEmptyList[B], NonEmptyList[C]]] = {
val ior = eval.value
(f(a), ior) match {
case (Right(c), Ior.Left(_)) => Eval.now(ior.putRight(NonEmptyList.one(c)))
case (Right(c), _) => Eval.now(ior.map(c :: _))
case (Left(b), Ior.Right(r)) => Eval.now(Ior.bothNel(b, r))
case (Left(b), _) => Eval.now(ior.leftMap(b :: _))
}
}

reduceRightTo(fa)(a => f(a).bimap(NonEmptyList.one, NonEmptyList.one).toIor)(g).value
}

override def isEmpty[A](fa: F[A]): Boolean = false

override def nonEmpty[A](fa: F[A]): Boolean = true
Expand Down
14 changes: 0 additions & 14 deletions core/src/main/scala/cats/data/NonEmptyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -320,20 +320,6 @@ final case class NonEmptyList[+A](head: A, tail: List[A]) {
b.result
}

def partitionE[B, C](f: A => Either[B, C]): Ior[NonEmptyList[B], NonEmptyList[C]] = {
import cats.syntax.either._

val reversed = reverse
val lastIor = f(reversed.head).bimap(NonEmptyList.one, NonEmptyList.one).toIor

reversed.tail.foldLeft(lastIor)((ior, a) => (f(a), ior) match {
case (Right(c), Ior.Left(l)) => ior.putRight(NonEmptyList.one(c))
case (Right(c), _) => ior.map(c :: _)
case (Left(b), Ior.Right(r)) => Ior.bothNel(b, r)
case (Left(b), _) => ior.leftMap(b :: _)
})
}

}

object NonEmptyList extends NonEmptyListInstances {
Expand Down
30 changes: 0 additions & 30 deletions tests/src/test/scala/cats/tests/NonEmptyListTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,36 +74,6 @@ class NonEmptyListTests extends CatsSuite {
}
}

test("NonEmptyList#partitionE retains size") {
forAll { (nel: NonEmptyList[Int], f: Int => Either[String, String]) =>
val folded = nel.partitionE(f).fold(identity, identity, _ ++ _.toList)
folded.size should === (nel.size)
}
}

test("NonEmptyList#partitionE to one side is identity") {
forAll { (nel: NonEmptyList[Int], f: Int => String) =>
val g: Int => Either[Double, String] = f andThen Right.apply
val h: Int => Either[String, Double] = f andThen Left.apply

val withG = nel.partitionE(g).fold(_ => NonEmptyList.one(""), identity, (l,r) => r)
withG should === (nel.map(f))

val withH = nel.partitionE(h).fold(identity, _ => NonEmptyList.one(""), (l,r) => l)
withH should === (nel.map(f))
}
}

test("NonEmptyList#partitionE remains sorted") {
forAll { (nel: NonEmptyList[Int], f: Int => Either[String, String]) =>

val sorted = nel.map(f).sorted
val ior = sorted.partitionE(identity)

ior.left.map(xs => xs.sorted should === (xs))
ior.right.map(xs => xs.sorted should === (xs))
}
}

test("NonEmptyList#filter is consistent with List#filter") {
forAll { (nel: NonEmptyList[Int], p: Int => Boolean) =>
Expand Down
35 changes: 34 additions & 1 deletion tests/src/test/scala/cats/tests/ReducibleTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class ReducibleTestsAdditional extends CatsSuite {

}

abstract class ReducibleCheck[F[_]: Reducible](name: String)(implicit ArbFInt: Arbitrary[F[Int]], ArbFString: Arbitrary[F[String]]) extends FoldableCheck[F](name) {
abstract class ReducibleCheck[F[_]: Reducible: Functor](name: String)(implicit ArbFInt: Arbitrary[F[Int]], ArbFString: Arbitrary[F[String]]) extends FoldableCheck[F](name) {
def range(start: Long, endInclusive: Long): F[Long]

test(s"Reducible[$name].reduceLeftM stack safety") {
Expand All @@ -95,4 +95,37 @@ abstract class ReducibleCheck[F[_]: Reducible](name: String)(implicit ArbFInt: A
fa.nonEmptyIntercalate(a) === (fa.toList.mkString(a))
}
}


test("Reducible#partitionE retains size") {
forAll { (fi: F[Int], f: Int => Either[String, String]) =>
val folded = fi.partitionE(f).fold(identity, identity, _ ++ _.toList)
folded.size.toLong should === (fi.size)
}
}

test("Reducible#partitionE to one side is identity") {
forAll { (fi: F[Int], f: Int => String) =>
val g: Int => Either[Double, String] = f andThen Right.apply
val h: Int => Either[String, Double] = f andThen Left.apply

val withG = fi.partitionE(g).fold(_ => NonEmptyList.one(""), identity, (l,r) => r)
withG should === (Reducible[F].toNonEmptyList((fi.map(f))))

val withH = fi.partitionE(h).fold(identity, _ => NonEmptyList.one(""), (l,r) => l)
withH should === (Reducible[F].toNonEmptyList((fi.map(f))))
}
}

test("Reducible#partitionE remains sorted") {
forAll { (fi: F[Int], f: Int => Either[String, String]) =>
val nel = Reducible[F].toNonEmptyList(fi)

val sorted = nel.map(f).sorted
val ior = sorted.partitionE(identity)

ior.left.map(xs => xs.sorted should === (xs))
ior.right.map(xs => xs.sorted should === (xs))
}
}
}

0 comments on commit 3be4721

Please sign in to comment.