-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Traverse1 typeclass (#1577) #1611
Changes from 26 commits
5452d01
96d79d2
4a38545
3e9afe3
7ff5e1e
2e2e7ac
82f4537
ab40d79
11e8a5d
6945a26
17d8d62
b1e115b
796ebb4
f437a98
647df06
1f69c30
caac5f7
aa5d6aa
590ba54
dfbc064
9093a53
5c49d9f
0cc4531
a2bbee4
8e9656b
f214c64
9ed10ea
7f55edc
02c25d5
50cc4c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package cats | ||
|
||
import simulacrum.typeclass | ||
|
||
/** | ||
* Traverse1, also known as Traversable1. | ||
* | ||
* `Traverse1` is like a non-empty `Traverse`. In addition to the traverse and sequence | ||
* methods it provides traverse1 and sequence1 methods which require an `Apply` instance instead of `Applicative`. | ||
*/ | ||
@typeclass trait Traverse1[F[_]] extends Traverse[F] with Reducible[F] { self => | ||
|
||
/** | ||
* Given a function which returns a G effect, thread this effect | ||
* through the running of this function on all the values in F, | ||
* returning an F[B] in a G context. | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> import cats.implicits._ | ||
* scala> import cats.data.NonEmptyList | ||
* scala> def countWords(words: List[String]): Map[String, Int] = words.groupBy(identity).mapValues(_.length) | ||
* scala> NonEmptyList.of(List("How", "do", "you", "fly"), List("What", "do", "you", "do")).traverse1(countWords) | ||
* res0: Map[String,cats.data.NonEmptyList[Int]] = Map(do -> NonEmptyList(1, 2), you -> NonEmptyList(1, 1)) | ||
* }}} | ||
*/ | ||
def traverse1[G[_]: Apply, A, B](fa: F[A])(f: A => G[B]): G[F[B]] | ||
|
||
/** | ||
* Thread all the G effects through the F structure to invert the | ||
* structure from F[G[A]] to G[F[A]]. | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> import cats.implicits._ | ||
* scala> import cats.data.NonEmptyList | ||
* scala> val x = NonEmptyList.of(Map("do" -> 1, "you" -> 1), Map("do" -> 2, "you" -> 1)) | ||
* scala> val y = NonEmptyList.of(Map("How" -> 3, "do" -> 1, "you" -> 1), Map[String,Int]()) | ||
* scala> x.sequence1 | ||
* res0: Map[String,NonEmptyList[Int]] = Map(do -> NonEmptyList(1, 2), you -> NonEmptyList(1, 1)) | ||
* scala> y.sequence1 | ||
* res1: Map[String,NonEmptyList[Int]] = Map() | ||
* }}} | ||
*/ | ||
def sequence1[G[_]: Apply, A](fga: F[G[A]]): G[F[A]] = | ||
traverse1(fga)(identity) | ||
|
||
|
||
/** | ||
* A traverse1 followed by flattening the inner result. | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> import cats.implicits._ | ||
* scala> import cats.data.NonEmptyList | ||
* scala> def countWords(words: List[String]): Map[String, Int] = words.groupBy(identity).mapValues(_.length) | ||
* scala> val x = NonEmptyList.of(List("How", "do", "you", "fly"), List("What", "do", "you", "do")) | ||
* scala> x.flatTraverse1(_.groupByNel(identity)) | ||
* res0: Map[String,cats.data.NonEmptyList[String]] = Map(do -> NonEmptyList(do, do, do), you -> NonEmptyList(you, you)) | ||
* }}} | ||
*/ | ||
def flatTraverse1[G[_], A, B](fa: F[A])(f: A => G[F[B]])(implicit G: Apply[G], F: FlatMap[F]): G[F[B]] = | ||
G.map(traverse1(fa)(f))(F.flatten) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto (untested) for this, and |
||
|
||
/** | ||
* Thread all the G effects through the F structure and flatten to invert the | ||
* structure from F[G[F[A]]] to G[F[A]]. | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> import cats.implicits._ | ||
* scala> import cats.data.NonEmptyList | ||
* scala> val x = NonEmptyList.of(Map(0 ->NonEmptyList.of(1, 2)), Map(0 -> NonEmptyList.of(3))) | ||
* scala> val y: NonEmptyList[Map[Int, NonEmptyList[Int]]] = NonEmptyList.of(Map(), Map(1 -> NonEmptyList.of(3))) | ||
* scala> x.flatSequence1 | ||
* res0: Map[Int,cats.data.NonEmptyList[Int]] = Map(0 -> NonEmptyList(1, 2, 3)) | ||
* scala> y.flatSequence1 | ||
* res1: Map[Int,cats.data.NonEmptyList[Int]] = Map() | ||
* }}} | ||
*/ | ||
def flatSequence1[G[_], A](fgfa: F[G[F[A]]])(implicit G: Apply[G], F: FlatMap[F]): G[F[A]] = | ||
G.map(traverse1(fgfa)(identity))(F.flatten) | ||
|
||
override def traverse[G[_] : Applicative, A, B](fa: F[A])(f: (A) => G[B]): G[F[B]] = | ||
traverse1(fa)(f) | ||
|
||
def compose[G[_]: Traverse1]: Traverse1[λ[α => F[G[α]]]] = | ||
new ComposedTraverse1[F, G] { | ||
val F = self | ||
val G = Traverse1[G] | ||
} | ||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -133,13 +133,20 @@ private[data] sealed abstract class NestedInstances9 extends NestedInstances10 { | |
} | ||
} | ||
|
||
private[data] sealed abstract class NestedInstances10 { | ||
private[data] sealed abstract class NestedInstances10 extends NestedInstances11 { | ||
implicit def catsDataInvariantForNestedContravariant[F[_]: Invariant, G[_]: Contravariant]: Invariant[Nested[F, G, ?]] = | ||
new NestedInvariant[F, G] { | ||
val FG: Invariant[λ[α => F[G[α]]]] = Invariant[F].composeContravariant[G] | ||
} | ||
} | ||
|
||
private[data] sealed abstract class NestedInstances11 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if making a new class here was the best thing to do, feedback very welcome :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can put the As you can never have a |
||
implicit def catsDataTraverse1ForNested[F[_]: Traverse1, G[_]: Traverse1]: Traverse1[Nested[F, G, ?]] = | ||
new NestedTraverse1[F, G] { | ||
val FG: Traverse1[λ[α => F[G[α]]]] = Traverse1[F].compose[G] | ||
} | ||
} | ||
|
||
private[data] trait NestedInvariant[F[_], G[_]] extends Invariant[Nested[F, G, ?]] { | ||
def FG: Invariant[λ[α => F[G[α]]]] | ||
|
||
|
@@ -233,6 +240,13 @@ private[data] trait NestedReducible[F[_], G[_]] extends Reducible[Nested[F, G, ? | |
FG.reduceRightTo(fga.value)(f)(g) | ||
} | ||
|
||
private[data] trait NestedTraverse1[F[_], G[_]] extends Traverse1[Nested[F, G, ?]] with NestedTraverse[F, G] with NestedReducible[F, G] { | ||
def FG: Traverse1[λ[α => F[G[α]]]] | ||
|
||
override def traverse1[H[_]: Apply, A, B](fga: Nested[F, G, A])(f: A => H[B]): H[Nested[F, G, B]] = | ||
Apply[H].map(FG.traverse1(fga.value)(f))(Nested(_)) | ||
} | ||
|
||
private[data] trait NestedContravariant[F[_], G[_]] extends Contravariant[Nested[F, G, ?]] { | ||
def FG: Contravariant[λ[α => F[G[α]]]] | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,6 +54,10 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) { | |
def forall(p: A => Boolean)(implicit F: Foldable[F]): Boolean = | ||
p(head) && F.forall(tail)(p) | ||
|
||
|
||
def reduceLeft(f: (A, A) => A)(implicit F: Foldable[F]): A = | ||
F.foldLeft(tail, head)(f) | ||
|
||
/** | ||
* Left-associative fold on the structure using f. | ||
*/ | ||
|
@@ -94,7 +98,7 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) { | |
s"OneAnd(${A.show(head)}, ${FA.show(tail)})" | ||
} | ||
|
||
private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { | ||
private[data] sealed trait OneAndInstances extends OneAndLowPriority3 { | ||
|
||
implicit def catsDataEqForOneAnd[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[F, A]] = | ||
new Eq[OneAnd[F, A]]{ | ||
|
@@ -221,4 +225,22 @@ private[data] trait OneAndLowPriority2 extends OneAndLowPriority1 { | |
} | ||
} | ||
|
||
private[data] trait OneAndLowPriority3 extends OneAndLowPriority2 { | ||
implicit def catsDataTraverse1ForOneAnd[F[_]](implicit F: Traverse[F], F2: MonadCombine[F]): Traverse1[OneAnd[F, ?]] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we override This implementation using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great idea! |
||
new NonEmptyReducible[OneAnd[F, ?], F] with Traverse1[OneAnd[F, ?]] { | ||
def traverse1[G[_], A, B](fa: OneAnd[F, A])(f: (A) => G[B])(implicit G: Apply[G]): G[OneAnd[F, B]] = { | ||
import cats.syntax.cartesian._ | ||
|
||
fa.map(a => Apply[G].map(f(a))(OneAnd(_, F2.empty[B])))(F) | ||
.reduceLeft(((acc, a) => (acc |@| a).map((x: OneAnd[F, B], y: OneAnd[F, B]) => x.combine(y)))) | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add the implementation of |
||
override def traverse[G[_], A, B](fa: OneAnd[F, A])(f: (A) => G[B])(implicit G: Applicative[G]): G[OneAnd[F, B]] = { | ||
G.map2Eval(f(fa.head), Always(F.traverse(fa.tail)(f)))(OneAnd(_, _)).value | ||
} | ||
|
||
def split[A](fa: OneAnd[F, A]): (A, F[A]) = (fa.head, fa.tail) | ||
} | ||
} | ||
|
||
object OneAnd extends OneAndInstances |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package cats | ||
package syntax | ||
|
||
trait Traverse1Syntax extends Traverse1.ToTraverse1Ops |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package cats.laws | ||
|
||
|
||
import cats.{Apply, Id, Semigroup, Traverse1} | ||
import cats.data.{Const, Nested} | ||
import cats.syntax.traverse1._ | ||
import cats.syntax.reducible._ | ||
|
||
trait Traverse1Laws[F[_]] extends TraverseLaws[F] with ReducibleLaws[F] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None of the laws and test were actually hit. We need to add one law test in a data type that has a vanilla |
||
implicit override def F: Traverse1[F] | ||
|
||
def traverse1Identity[A, B](fa: F[A], f: A => B): IsEq[F[B]] = { | ||
fa.traverse1[Id, B](f) <-> F.map(fa)(f) | ||
} | ||
|
||
def traverse1SequentialComposition[A, B, C, M[_], N[_]]( | ||
fa: F[A], | ||
f: A => M[B], | ||
g: B => N[C] | ||
)(implicit | ||
N: Apply[N], | ||
M: Apply[M] | ||
): IsEq[Nested[M, N, F[C]]] = { | ||
|
||
val lhs = Nested(M.map(fa.traverse1(f))(fb => fb.traverse1(g))) | ||
val rhs = fa.traverse1[Nested[M, N, ?], C](a => Nested(M.map(f(a))(g))) | ||
lhs <-> rhs | ||
} | ||
|
||
def traverse1ParallelComposition[A, B, M[_], N[_]]( | ||
fa: F[A], | ||
f: A => M[B], | ||
g: A => N[B] | ||
)(implicit | ||
N: Apply[N], | ||
M: Apply[M] | ||
): IsEq[(M[F[B]], N[F[B]])] = { | ||
type MN[Z] = (M[Z], N[Z]) | ||
implicit val MN = new Apply[MN] { | ||
def ap[X, Y](f: MN[X => Y])(fa: MN[X]): MN[Y] = { | ||
val (fam, fan) = fa | ||
val (fm, fn) = f | ||
(M.ap(fm)(fam), N.ap(fn)(fan)) | ||
} | ||
override def map[X, Y](fx: MN[X])(f: X => Y): MN[Y] = { | ||
val (mx, nx) = fx | ||
(M.map(mx)(f), N.map(nx)(f)) | ||
} | ||
override def product[X, Y](fx: MN[X], fy: MN[Y]): MN[(X, Y)] = { | ||
val (mx, nx) = fx | ||
val (my, ny) = fy | ||
(M.product(mx, my), N.product(nx, ny)) | ||
} | ||
} | ||
val lhs: MN[F[B]] = fa.traverse1[MN, B](a => (f(a), g(a))) | ||
val rhs: MN[F[B]] = (fa.traverse1(f), fa.traverse1(g)) | ||
lhs <-> rhs | ||
} | ||
|
||
def reduceMapDerived[A, B]( | ||
fa: F[A], | ||
f: A => B | ||
)(implicit B: Semigroup[B]): IsEq[B] = { | ||
val lhs: B = fa.traverse1[Const[B, ?], B](a => Const(f(a))).getConst | ||
val rhs: B = fa.reduceMap(f) | ||
lhs <-> rhs | ||
} | ||
} | ||
|
||
object Traverse1Laws { | ||
def apply[F[_]](implicit ev: Traverse1[F]): Traverse1Laws[F] = | ||
new Traverse1Laws[F] { def F: Traverse1[F] = ev } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is untested.