Skip to content

Commit afb8a18

Browse files
LukaJCBkailuowang
authored andcommitted
Add Traverse1 typeclass (#1577) (#1611)
* Add Traverse1 typeclass (#1577) * Add tests and laws for Traverse1 typeclass (#1577) * Format with newline at end of file * Replace NonEmptyReducible with NonEmptyTraverse1 * Add Scaladocs to Traverse1 * Remove unneeded Unapply version * Readd Reducible instance to OneAnd so a Monad is not needed for a Foldable instance * Use NonEmptyTraverse1 for traverse1 implementation of OneAnd * Rename flatSequence * Remove NonEmptyReducible class and replace with NonEmptyReducible + Traverse1 * Replace traverse1 with more performant implementation * Remove Unapply syntax * Separate traverse and traverse1 for OneAnd * Override traverse implementation of NonEmptyVector * Change type annotation of NonEmptyvector instances to reflect actual type * Add Law tests for traverse1 instances of NonEmptyList and Vector * Correct inheritance for OneAnd * Add traverse1 law testing for OneAnd instance * Add doctests for traverse1 and sequence1 * Add remaining doctests * Override traverse in traverse1 instance of OneAnd * Fix Indentation * Remove redundant tests and replace with Traverse1 tests * Add Traverse1#compose and instance for Nested * Move nested traverse1 instance to NestedInstances * Move reducible nested tests * rename traverse1 to nonEmptyTraverse * Rename intercalate1 to nonEmptyIntercalate
1 parent 3ba8993 commit afb8a18

22 files changed

+358
-35
lines changed

core/src/main/scala/cats/Composed.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ private[cats] trait ComposedTraverse[F[_], G[_]] extends Traverse[λ[α => F[G[
7171
F.traverse(fga)(ga => G.traverse(ga)(f))
7272
}
7373

74+
private[cats] trait ComposedNonEmptyTraverse[F[_], G[_]] extends NonEmptyTraverse[λ[α => F[G[α]]]] with ComposedTraverse[F, G] with ComposedReducible[F, G] {
75+
def F: NonEmptyTraverse[F]
76+
def G: NonEmptyTraverse[G]
77+
78+
override def nonEmptyTraverse[H[_]: Apply, A, B](fga: F[G[A]])(f: A => H[B]): H[F[G[B]]] =
79+
F.nonEmptyTraverse(fga)(ga => G.nonEmptyTraverse(ga)(f))
80+
}
81+
7482
private[cats] trait ComposedTraverseFilter[F[_], G[_]] extends TraverseFilter[λ[α => F[G[α]]]] with ComposedTraverse[F, G] {
7583
def F: Traverse[F]
7684
def G: TraverseFilter[G]
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package cats
2+
3+
import simulacrum.typeclass
4+
5+
/**
6+
* NonEmptyTraverse, also known as Traversable1.
7+
*
8+
* `NonEmptyTraverse` is like a non-empty `Traverse`. In addition to the traverse and sequence
9+
* methods it provides nonEmptyTraverse and nonEmptySequence methods which require an `Apply` instance instead of `Applicative`.
10+
*/
11+
@typeclass trait NonEmptyTraverse[F[_]] extends Traverse[F] with Reducible[F] { self =>
12+
13+
/**
14+
* Given a function which returns a G effect, thread this effect
15+
* through the running of this function on all the values in F,
16+
* returning an F[B] in a G context.
17+
*
18+
* Example:
19+
* {{{
20+
* scala> import cats.implicits._
21+
* scala> import cats.data.NonEmptyList
22+
* scala> def countWords(words: List[String]): Map[String, Int] = words.groupBy(identity).mapValues(_.length)
23+
* scala> NonEmptyList.of(List("How", "do", "you", "fly"), List("What", "do", "you", "do")).nonEmptyTraverse(countWords)
24+
* res0: Map[String,cats.data.NonEmptyList[Int]] = Map(do -> NonEmptyList(1, 2), you -> NonEmptyList(1, 1))
25+
* }}}
26+
*/
27+
def nonEmptyTraverse[G[_]: Apply, A, B](fa: F[A])(f: A => G[B]): G[F[B]]
28+
29+
/**
30+
* Thread all the G effects through the F structure to invert the
31+
* structure from F[G[A]] to G[F[A]].
32+
*
33+
* Example:
34+
* {{{
35+
* scala> import cats.implicits._
36+
* scala> import cats.data.NonEmptyList
37+
* scala> val x = NonEmptyList.of(Map("do" -> 1, "you" -> 1), Map("do" -> 2, "you" -> 1))
38+
* scala> val y = NonEmptyList.of(Map("How" -> 3, "do" -> 1, "you" -> 1), Map[String,Int]())
39+
* scala> x.nonEmptySequence
40+
* res0: Map[String,NonEmptyList[Int]] = Map(do -> NonEmptyList(1, 2), you -> NonEmptyList(1, 1))
41+
* scala> y.nonEmptySequence
42+
* res1: Map[String,NonEmptyList[Int]] = Map()
43+
* }}}
44+
*/
45+
def nonEmptySequence[G[_]: Apply, A](fga: F[G[A]]): G[F[A]] =
46+
nonEmptyTraverse(fga)(identity)
47+
48+
49+
/**
50+
* A nonEmptyTraverse followed by flattening the inner result.
51+
*
52+
* Example:
53+
* {{{
54+
* scala> import cats.implicits._
55+
* scala> import cats.data.NonEmptyList
56+
* scala> def countWords(words: List[String]): Map[String, Int] = words.groupBy(identity).mapValues(_.length)
57+
* scala> val x = NonEmptyList.of(List("How", "do", "you", "fly"), List("What", "do", "you", "do"))
58+
* scala> x.nonEmptyFlatTraverse(_.groupByNel(identity))
59+
* res0: Map[String,cats.data.NonEmptyList[String]] = Map(do -> NonEmptyList(do, do, do), you -> NonEmptyList(you, you))
60+
* }}}
61+
*/
62+
def nonEmptyFlatTraverse[G[_], A, B](fa: F[A])(f: A => G[F[B]])(implicit G: Apply[G], F: FlatMap[F]): G[F[B]] =
63+
G.map(nonEmptyTraverse(fa)(f))(F.flatten)
64+
65+
/**
66+
* Thread all the G effects through the F structure and flatten to invert the
67+
* structure from F[G[F[A]]] to G[F[A]].
68+
*
69+
* Example:
70+
* {{{
71+
* scala> import cats.implicits._
72+
* scala> import cats.data.NonEmptyList
73+
* scala> val x = NonEmptyList.of(Map(0 ->NonEmptyList.of(1, 2)), Map(0 -> NonEmptyList.of(3)))
74+
* scala> val y: NonEmptyList[Map[Int, NonEmptyList[Int]]] = NonEmptyList.of(Map(), Map(1 -> NonEmptyList.of(3)))
75+
* scala> x.nonEmptyFlatSequence
76+
* res0: Map[Int,cats.data.NonEmptyList[Int]] = Map(0 -> NonEmptyList(1, 2, 3))
77+
* scala> y.nonEmptyFlatSequence
78+
* res1: Map[Int,cats.data.NonEmptyList[Int]] = Map()
79+
* }}}
80+
*/
81+
def nonEmptyFlatSequence[G[_], A](fgfa: F[G[F[A]]])(implicit G: Apply[G], F: FlatMap[F]): G[F[A]] =
82+
G.map(nonEmptyTraverse(fgfa)(identity))(F.flatten)
83+
84+
override def traverse[G[_] : Applicative, A, B](fa: F[A])(f: (A) => G[B]): G[F[B]] =
85+
nonEmptyTraverse(fa)(f)
86+
87+
def compose[G[_]: NonEmptyTraverse]: NonEmptyTraverse[λ[α => F[G[α]]]] =
88+
new ComposedNonEmptyTraverse[F, G] {
89+
val F = self
90+
val G = NonEmptyTraverse[G]
91+
}
92+
93+
94+
}

core/src/main/scala/cats/Reducible.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,17 @@ import simulacrum.typeclass
127127
* available for `G` and want to take advantage of short-circuiting
128128
* the traversal.
129129
*/
130-
def traverse1_[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: Apply[G]): G[Unit] =
130+
def nonEmptyTraverse_[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: Apply[G]): G[Unit] =
131131
G.map(reduceLeftTo(fa)(f)((x, y) => G.map2(x, f(y))((_, b) => b)))(_ => ())
132132

133133
/**
134134
* Sequence `F[G[A]]` using `Apply[G]`.
135135
*
136136
* This method is similar to [[Foldable.sequence_]] but requires only
137137
* an [[Apply]] instance for `G` instead of [[Applicative]]. See the
138-
* [[traverse1_]] documentation for a description of the differences.
138+
* [[nonEmptyTraverse_]] documentation for a description of the differences.
139139
*/
140-
def sequence1_[G[_], A](fga: F[G[A]])(implicit G: Apply[G]): G[Unit] =
140+
def nonEmptySequence_[G[_], A](fga: F[G[A]])(implicit G: Apply[G]): G[Unit] =
141141
G.map(reduceLeft(fga)((x, y) => G.map2(x, y)((_, b) => b)))(_ => ())
142142

143143
def toNonEmptyList[A](fa: F[A]): NonEmptyList[A] =
@@ -164,13 +164,13 @@ import simulacrum.typeclass
164164
* scala> import cats.implicits._
165165
* scala> import cats.data.NonEmptyList
166166
* scala> val nel = NonEmptyList.of("a", "b", "c")
167-
* scala> Reducible[NonEmptyList].intercalate1(nel, "-")
167+
* scala> Reducible[NonEmptyList].nonEmptyIntercalate(nel, "-")
168168
* res0: String = a-b-c
169-
* scala> Reducible[NonEmptyList].intercalate1(NonEmptyList.of("a"), "-")
169+
* scala> Reducible[NonEmptyList].nonEmptyIntercalate(NonEmptyList.of("a"), "-")
170170
* res1: String = a
171171
* }}}
172172
*/
173-
def intercalate1[A](fa: F[A], a: A)(implicit A: Semigroup[A]): A =
173+
def nonEmptyIntercalate[A](fa: F[A], a: A)(implicit A: Semigroup[A]): A =
174174
toNonEmptyList(fa) match {
175175
case NonEmptyList(hd, Nil) => hd
176176
case NonEmptyList(hd, tl) =>

core/src/main/scala/cats/data/Nested.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ private[data] sealed abstract class NestedInstances extends NestedInstances0 {
3535
new NestedTraverseFilter[F, G] {
3636
val FG: TraverseFilter[λ[α => F[G[α]]]] = Traverse[F].composeFilter[G]
3737
}
38+
39+
implicit def catsDataNonEmptyTraverseForNested[F[_]: NonEmptyTraverse, G[_]: NonEmptyTraverse]: NonEmptyTraverse[Nested[F, G, ?]] =
40+
new NestedNonEmptyTraverse[F, G] {
41+
val FG: NonEmptyTraverse[λ[α => F[G[α]]]] = NonEmptyTraverse[F].compose[G]
42+
}
3843
}
3944

4045
private[data] sealed abstract class NestedInstances0 extends NestedInstances1 {
@@ -233,6 +238,13 @@ private[data] trait NestedReducible[F[_], G[_]] extends Reducible[Nested[F, G, ?
233238
FG.reduceRightTo(fga.value)(f)(g)
234239
}
235240

241+
private[data] trait NestedNonEmptyTraverse[F[_], G[_]] extends NonEmptyTraverse[Nested[F, G, ?]] with NestedTraverse[F, G] with NestedReducible[F, G] {
242+
def FG: NonEmptyTraverse[λ[α => F[G[α]]]]
243+
244+
override def nonEmptyTraverse[H[_]: Apply, A, B](fga: Nested[F, G, A])(f: A => H[B]): H[Nested[F, G, B]] =
245+
Apply[H].map(FG.nonEmptyTraverse(fga.value)(f))(Nested(_))
246+
}
247+
236248
private[data] trait NestedContravariant[F[_], G[_]] extends Contravariant[Nested[F, G, ?]] {
237249
def FG: Contravariant[λ[α => F[G[α]]]]
238250

core/src/main/scala/cats/data/NonEmptyList.scala

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,9 @@ object NonEmptyList extends NonEmptyListInstances {
367367
private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 {
368368

369369
implicit val catsDataInstancesForNonEmptyList: SemigroupK[NonEmptyList] with Reducible[NonEmptyList]
370-
with Comonad[NonEmptyList] with Traverse[NonEmptyList] with Monad[NonEmptyList] =
370+
with Comonad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] with Monad[NonEmptyList] =
371371
new NonEmptyReducible[NonEmptyList, List] with SemigroupK[NonEmptyList] with Comonad[NonEmptyList]
372-
with Traverse[NonEmptyList] with Monad[NonEmptyList] {
372+
with Monad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] {
373373

374374
def combineK[A](a: NonEmptyList[A], b: NonEmptyList[A]): NonEmptyList[A] =
375375
a concat b
@@ -396,7 +396,15 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0
396396

397397
def extract[A](fa: NonEmptyList[A]): A = fa.head
398398

399-
def traverse[G[_], A, B](fa: NonEmptyList[A])(f: A => G[B])(implicit G: Applicative[G]): G[NonEmptyList[B]] =
399+
def nonEmptyTraverse[G[_], A, B](nel: NonEmptyList[A])(f: A => G[B])(implicit G: Apply[G]): G[NonEmptyList[B]] =
400+
Foldable[List].reduceRightToOption[A, G[List[B]]](nel.tail)(a => G.map(f(a))(_ :: Nil)) { (a, lglb) =>
401+
G.map2Eval(f(a), lglb)(_ :: _)
402+
}.map {
403+
case None => G.map(f(nel.head))(NonEmptyList(_, Nil))
404+
case Some(gtail) => G.map2(f(nel.head), gtail)(NonEmptyList(_, _))
405+
}.value
406+
407+
override def traverse[G[_], A, B](fa: NonEmptyList[A])(f: A => G[B])(implicit G: Applicative[G]): G[NonEmptyList[B]] =
400408
fa traverse f
401409

402410
override def foldLeft[A, B](fa: NonEmptyList[A], b: B)(f: (B, A) => B): B =

core/src/main/scala/cats/data/NonEmptyVector.scala

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,9 @@ final class NonEmptyVector[+A] private (val toVector: Vector[A]) extends AnyVal
189189
private[data] sealed trait NonEmptyVectorInstances {
190190

191191
implicit val catsDataInstancesForNonEmptyVector: SemigroupK[NonEmptyVector] with Reducible[NonEmptyVector]
192-
with Comonad[NonEmptyVector] with Traverse[NonEmptyVector] with Monad[NonEmptyVector] =
192+
with Comonad[NonEmptyVector] with NonEmptyTraverse[NonEmptyVector] with Monad[NonEmptyVector] =
193193
new NonEmptyReducible[NonEmptyVector, Vector] with SemigroupK[NonEmptyVector] with Comonad[NonEmptyVector]
194-
with Traverse[NonEmptyVector] with Monad[NonEmptyVector] {
194+
with Monad[NonEmptyVector] with NonEmptyTraverse[NonEmptyVector] {
195195

196196
def combineK[A](a: NonEmptyVector[A], b: NonEmptyVector[A]): NonEmptyVector[A] =
197197
a concatNev b
@@ -226,7 +226,15 @@ private[data] sealed trait NonEmptyVectorInstances {
226226

227227
def extract[A](fa: NonEmptyVector[A]): A = fa.head
228228

229-
def traverse[G[_], A, B](fa: NonEmptyVector[A])(f: (A) => G[B])(implicit G: Applicative[G]): G[NonEmptyVector[B]] =
229+
def nonEmptyTraverse[G[_], A, B](nel: NonEmptyVector[A])(f: A => G[B])(implicit G: Apply[G]): G[NonEmptyVector[B]] =
230+
Foldable[Vector].reduceRightToOption[A, G[Vector[B]]](nel.tail)(a => G.map(f(a))(_ +: Vector.empty)) { (a, lglb) =>
231+
G.map2Eval(f(a), lglb)(_ +: _)
232+
}.map {
233+
case None => G.map(f(nel.head))(NonEmptyVector(_, Vector.empty))
234+
case Some(gtail) => G.map2(f(nel.head), gtail)(NonEmptyVector(_, _))
235+
}.value
236+
237+
override def traverse[G[_], A, B](fa: NonEmptyVector[A])(f: (A) => G[B])(implicit G: Applicative[G]): G[NonEmptyVector[B]] =
230238
G.map2Eval(f(fa.head), Always(Traverse[Vector].traverse(fa.tail)(f)))(NonEmptyVector(_, _)).value
231239

232240
override def foldLeft[A, B](fa: NonEmptyVector[A], b: B)(f: (B, A) => B): B =

core/src/main/scala/cats/data/OneAnd.scala

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) {
5454
def forall(p: A => Boolean)(implicit F: Foldable[F]): Boolean =
5555
p(head) && F.forall(tail)(p)
5656

57+
58+
def reduceLeft(f: (A, A) => A)(implicit F: Foldable[F]): A =
59+
F.foldLeft(tail, head)(f)
60+
5761
/**
5862
* Left-associative fold on the structure using f.
5963
*/
@@ -94,7 +98,7 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) {
9498
s"OneAnd(${A.show(head)}, ${FA.show(tail)})"
9599
}
96100

97-
private[data] sealed trait OneAndInstances extends OneAndLowPriority2 {
101+
private[data] sealed trait OneAndInstances extends OneAndLowPriority3 {
98102

99103
implicit def catsDataEqForOneAnd[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[F, A]] =
100104
new Eq[OneAnd[F, A]]{
@@ -221,4 +225,23 @@ private[data] trait OneAndLowPriority2 extends OneAndLowPriority1 {
221225
}
222226
}
223227

228+
private[data] trait OneAndLowPriority3 extends OneAndLowPriority2 {
229+
implicit def catsDataNonEmptyTraverseForOneAnd[F[_]](implicit F: Traverse[F], F2: MonadCombine[F]): NonEmptyTraverse[OneAnd[F, ?]] =
230+
new NonEmptyReducible[OneAnd[F, ?], F] with NonEmptyTraverse[OneAnd[F, ?]] {
231+
def nonEmptyTraverse[G[_], A, B](fa: OneAnd[F, A])(f: (A) => G[B])(implicit G: Apply[G]): G[OneAnd[F, B]] = {
232+
import cats.syntax.cartesian._
233+
234+
fa.map(a => Apply[G].map(f(a))(OneAnd(_, F2.empty[B])))(F)
235+
.reduceLeft(((acc, a) => (acc |@| a).map((x: OneAnd[F, B], y: OneAnd[F, B]) => x.combine(y))))
236+
}
237+
238+
239+
override def traverse[G[_], A, B](fa: OneAnd[F, A])(f: (A) => G[B])(implicit G: Applicative[G]): G[OneAnd[F, B]] = {
240+
G.map2Eval(f(fa.head), Always(F.traverse(fa.tail)(f)))(OneAnd(_, _)).value
241+
}
242+
243+
def split[A](fa: OneAnd[F, A]): (A, F[A]) = (fa.head, fa.tail)
244+
}
245+
}
246+
224247
object OneAnd extends OneAndInstances

core/src/main/scala/cats/package.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ package object cats {
3232
* encodes pure unary function application.
3333
*/
3434
type Id[A] = A
35-
implicit val catsInstancesForId: Bimonad[Id] with Monad[Id] with Traverse[Id] with Reducible[Id] =
36-
new Bimonad[Id] with Monad[Id] with Traverse[Id] with Reducible[Id] {
35+
implicit val catsInstancesForId: Bimonad[Id] with Monad[Id] with NonEmptyTraverse[Id] =
36+
new Bimonad[Id] with Monad[Id] with NonEmptyTraverse[Id] {
3737
def pure[A](a: A): A = a
3838
def extract[A](a: A): A = a
3939
def flatMap[A, B](a: A)(f: A => B): B = f(a)
@@ -51,7 +51,7 @@ package object cats {
5151
def foldLeft[A, B](a: A, b: B)(f: (B, A) => B) = f(b, a)
5252
def foldRight[A, B](a: A, lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
5353
f(a, lb)
54-
def traverse[G[_], A, B](a: A)(f: A => G[B])(implicit G: Applicative[G]): G[B] =
54+
def nonEmptyTraverse[G[_], A, B](a: A)(f: A => G[B])(implicit G: Apply[G]): G[B] =
5555
f(a)
5656
override def foldMap[A, B](fa: Id[A])(f: A => B)(implicit B: Monoid[B]): B = f(fa)
5757
override def reduce[A](fa: Id[A])(implicit A: Semigroup[A]): A =

core/src/main/scala/cats/syntax/all.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ trait AllSyntax
4242
with StrongSyntax
4343
with TraverseFilterSyntax
4444
with TraverseSyntax
45+
with NonEmptyTraverseSyntax
4546
with TupleSyntax
4647
with ValidatedSyntax
4748
with VectorSyntax
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package cats
2+
package syntax
3+
4+
trait NonEmptyTraverseSyntax extends NonEmptyTraverse.ToNonEmptyTraverseOps

0 commit comments

Comments
 (0)