Skip to content
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

Merged
merged 30 commits into from
Jun 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5452d01
Add Traverse1 typeclass (#1577)
Mar 27, 2017
96d79d2
Add tests and laws for Traverse1 typeclass (#1577)
Apr 18, 2017
4a38545
Merge branch 'master' into master
Apr 22, 2017
3e9afe3
Format with newline at end of file
Apr 22, 2017
7ff5e1e
Replace NonEmptyReducible with NonEmptyTraverse1
May 18, 2017
2e2e7ac
Add Scaladocs to Traverse1
May 18, 2017
82f4537
Remove unneeded Unapply version
May 18, 2017
ab40d79
Readd Reducible instance to OneAnd so a Monad is not needed for a Fol…
May 18, 2017
11e8a5d
Use NonEmptyTraverse1 for traverse1 implementation of OneAnd
May 18, 2017
6945a26
Rename flatSequence
May 28, 2017
17d8d62
Remove NonEmptyReducible class and replace with NonEmptyReducible + T…
May 28, 2017
b1e115b
Replace traverse1 with more performant implementation
May 28, 2017
796ebb4
Merge branch 'master' into master
May 28, 2017
f437a98
Remove Unapply syntax
May 28, 2017
647df06
Separate traverse and traverse1 for OneAnd
May 28, 2017
1f69c30
Override traverse implementation of NonEmptyVector
May 28, 2017
caac5f7
Change type annotation of NonEmptyvector instances to reflect actual …
May 28, 2017
aa5d6aa
Add Law tests for traverse1 instances of NonEmptyList and Vector
May 28, 2017
590ba54
Correct inheritance for OneAnd
May 28, 2017
dfbc064
Add traverse1 law testing for OneAnd instance
May 28, 2017
9093a53
Add doctests for traverse1 and sequence1
May 28, 2017
5c49d9f
Add remaining doctests
May 28, 2017
0cc4531
Override traverse in traverse1 instance of OneAnd
May 28, 2017
a2bbee4
Fix Indentation
May 28, 2017
8e9656b
Remove redundant tests and replace with Traverse1 tests
May 28, 2017
f214c64
Add Traverse1#compose and instance for Nested
May 29, 2017
9ed10ea
Move nested traverse1 instance to NestedInstances
May 29, 2017
7f55edc
Move reducible nested tests
May 29, 2017
02c25d5
rename traverse1 to nonEmptyTraverse
Jun 8, 2017
50cc4c0
Rename intercalate1 to nonEmptyIntercalate
Jun 12, 2017
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
8 changes: 8 additions & 0 deletions core/src/main/scala/cats/Composed.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ private[cats] trait ComposedTraverse[F[_], G[_]] extends Traverse[λ[α => F[G[
F.traverse(fga)(ga => G.traverse(ga)(f))
}

private[cats] trait ComposedNonEmptyTraverse[F[_], G[_]] extends NonEmptyTraverse[λ[α => F[G[α]]]] with ComposedTraverse[F, G] with ComposedReducible[F, G] {
def F: NonEmptyTraverse[F]
def G: NonEmptyTraverse[G]

override def nonEmptyTraverse[H[_]: Apply, A, B](fga: F[G[A]])(f: A => H[B]): H[F[G[B]]] =
F.nonEmptyTraverse(fga)(ga => G.nonEmptyTraverse(ga)(f))
}

private[cats] trait ComposedTraverseFilter[F[_], G[_]] extends TraverseFilter[λ[α => F[G[α]]]] with ComposedTraverse[F, G] {
def F: Traverse[F]
def G: TraverseFilter[G]
Expand Down
94 changes: 94 additions & 0 deletions core/src/main/scala/cats/NonEmptyTraverse.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package cats

import simulacrum.typeclass

/**
* NonEmptyTraverse, also known as Traversable1.
*
* `NonEmptyTraverse` is like a non-empty `Traverse`. In addition to the traverse and sequence
* methods it provides nonEmptyTraverse and nonEmptySequence methods which require an `Apply` instance instead of `Applicative`.
*/
@typeclass trait NonEmptyTraverse[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")).nonEmptyTraverse(countWords)
* res0: Map[String,cats.data.NonEmptyList[Int]] = Map(do -> NonEmptyList(1, 2), you -> NonEmptyList(1, 1))
* }}}
*/
def nonEmptyTraverse[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.nonEmptySequence
* res0: Map[String,NonEmptyList[Int]] = Map(do -> NonEmptyList(1, 2), you -> NonEmptyList(1, 1))
* scala> y.nonEmptySequence
* res1: Map[String,NonEmptyList[Int]] = Map()
* }}}
*/
def nonEmptySequence[G[_]: Apply, A](fga: F[G[A]]): G[F[A]] =
nonEmptyTraverse(fga)(identity)


/**
* A nonEmptyTraverse 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.nonEmptyFlatTraverse(_.groupByNel(identity))
* res0: Map[String,cats.data.NonEmptyList[String]] = Map(do -> NonEmptyList(do, do, do), you -> NonEmptyList(you, you))
* }}}
*/
def nonEmptyFlatTraverse[G[_], A, B](fa: F[A])(f: A => G[F[B]])(implicit G: Apply[G], F: FlatMap[F]): G[F[B]] =
G.map(nonEmptyTraverse(fa)(f))(F.flatten)

/**
* 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.nonEmptyFlatSequence
* res0: Map[Int,cats.data.NonEmptyList[Int]] = Map(0 -> NonEmptyList(1, 2, 3))
* scala> y.nonEmptyFlatSequence
* res1: Map[Int,cats.data.NonEmptyList[Int]] = Map()
* }}}
*/
def nonEmptyFlatSequence[G[_], A](fgfa: F[G[F[A]]])(implicit G: Apply[G], F: FlatMap[F]): G[F[A]] =
G.map(nonEmptyTraverse(fgfa)(identity))(F.flatten)

override def traverse[G[_] : Applicative, A, B](fa: F[A])(f: (A) => G[B]): G[F[B]] =
nonEmptyTraverse(fa)(f)

def compose[G[_]: NonEmptyTraverse]: NonEmptyTraverse[λ[α => F[G[α]]]] =
new ComposedNonEmptyTraverse[F, G] {
val F = self
val G = NonEmptyTraverse[G]
}


}
12 changes: 6 additions & 6 deletions core/src/main/scala/cats/Reducible.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,17 @@ import simulacrum.typeclass
* available for `G` and want to take advantage of short-circuiting
* the traversal.
*/
def traverse1_[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: Apply[G]): G[Unit] =
def nonEmptyTraverse_[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: Apply[G]): G[Unit] =
G.map(reduceLeftTo(fa)(f)((x, y) => G.map2(x, f(y))((_, b) => b)))(_ => ())

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

def toNonEmptyList[A](fa: F[A]): NonEmptyList[A] =
Expand All @@ -164,13 +164,13 @@ import simulacrum.typeclass
* scala> import cats.implicits._
* scala> import cats.data.NonEmptyList
* scala> val nel = NonEmptyList.of("a", "b", "c")
* scala> Reducible[NonEmptyList].intercalate1(nel, "-")
* scala> Reducible[NonEmptyList].nonEmptyIntercalate(nel, "-")
* res0: String = a-b-c
* scala> Reducible[NonEmptyList].intercalate1(NonEmptyList.of("a"), "-")
* scala> Reducible[NonEmptyList].nonEmptyIntercalate(NonEmptyList.of("a"), "-")
* res1: String = a
* }}}
*/
def intercalate1[A](fa: F[A], a: A)(implicit A: Semigroup[A]): A =
def nonEmptyIntercalate[A](fa: F[A], a: A)(implicit A: Semigroup[A]): A =
toNonEmptyList(fa) match {
case NonEmptyList(hd, Nil) => hd
case NonEmptyList(hd, tl) =>
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/scala/cats/data/Nested.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ private[data] sealed abstract class NestedInstances extends NestedInstances0 {
new NestedTraverseFilter[F, G] {
val FG: TraverseFilter[λ[α => F[G[α]]]] = Traverse[F].composeFilter[G]
}

implicit def catsDataNonEmptyTraverseForNested[F[_]: NonEmptyTraverse, G[_]: NonEmptyTraverse]: NonEmptyTraverse[Nested[F, G, ?]] =
new NestedNonEmptyTraverse[F, G] {
val FG: NonEmptyTraverse[λ[α => F[G[α]]]] = NonEmptyTraverse[F].compose[G]
}
}

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

private[data] trait NestedNonEmptyTraverse[F[_], G[_]] extends NonEmptyTraverse[Nested[F, G, ?]] with NestedTraverse[F, G] with NestedReducible[F, G] {
def FG: NonEmptyTraverse[λ[α => F[G[α]]]]

override def nonEmptyTraverse[H[_]: Apply, A, B](fga: Nested[F, G, A])(f: A => H[B]): H[Nested[F, G, B]] =
Apply[H].map(FG.nonEmptyTraverse(fga.value)(f))(Nested(_))
}

private[data] trait NestedContravariant[F[_], G[_]] extends Contravariant[Nested[F, G, ?]] {
def FG: Contravariant[λ[α => F[G[α]]]]

Expand Down
14 changes: 11 additions & 3 deletions core/src/main/scala/cats/data/NonEmptyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,9 @@ object NonEmptyList extends NonEmptyListInstances {
private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 {

implicit val catsDataInstancesForNonEmptyList: SemigroupK[NonEmptyList] with Reducible[NonEmptyList]
with Comonad[NonEmptyList] with Traverse[NonEmptyList] with Monad[NonEmptyList] =
with Comonad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] with Monad[NonEmptyList] =
new NonEmptyReducible[NonEmptyList, List] with SemigroupK[NonEmptyList] with Comonad[NonEmptyList]
with Traverse[NonEmptyList] with Monad[NonEmptyList] {
with Monad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] {

def combineK[A](a: NonEmptyList[A], b: NonEmptyList[A]): NonEmptyList[A] =
a concat b
Expand All @@ -394,7 +394,15 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0

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

def traverse[G[_], A, B](fa: NonEmptyList[A])(f: A => G[B])(implicit G: Applicative[G]): G[NonEmptyList[B]] =
def nonEmptyTraverse[G[_], A, B](nel: NonEmptyList[A])(f: A => G[B])(implicit G: Apply[G]): G[NonEmptyList[B]] =
Foldable[List].reduceRightToOption[A, G[List[B]]](nel.tail)(a => G.map(f(a))(_ :: Nil)) { (a, lglb) =>
G.map2Eval(f(a), lglb)(_ :: _)
}.map {
case None => G.map(f(nel.head))(NonEmptyList(_, Nil))
case Some(gtail) => G.map2(f(nel.head), gtail)(NonEmptyList(_, _))
}.value

override def traverse[G[_], A, B](fa: NonEmptyList[A])(f: A => G[B])(implicit G: Applicative[G]): G[NonEmptyList[B]] =
fa traverse f

override def foldLeft[A, B](fa: NonEmptyList[A], b: B)(f: (B, A) => B): B =
Expand Down
14 changes: 11 additions & 3 deletions core/src/main/scala/cats/data/NonEmptyVector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,9 @@ final class NonEmptyVector[+A] private (val toVector: Vector[A]) extends AnyVal
private[data] sealed trait NonEmptyVectorInstances {

implicit val catsDataInstancesForNonEmptyVector: SemigroupK[NonEmptyVector] with Reducible[NonEmptyVector]
with Comonad[NonEmptyVector] with Traverse[NonEmptyVector] with Monad[NonEmptyVector] =
with Comonad[NonEmptyVector] with NonEmptyTraverse[NonEmptyVector] with Monad[NonEmptyVector] =
new NonEmptyReducible[NonEmptyVector, Vector] with SemigroupK[NonEmptyVector] with Comonad[NonEmptyVector]
with Traverse[NonEmptyVector] with Monad[NonEmptyVector] {
with Monad[NonEmptyVector] with NonEmptyTraverse[NonEmptyVector] {

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

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

def traverse[G[_], A, B](fa: NonEmptyVector[A])(f: (A) => G[B])(implicit G: Applicative[G]): G[NonEmptyVector[B]] =
def nonEmptyTraverse[G[_], A, B](nel: NonEmptyVector[A])(f: A => G[B])(implicit G: Apply[G]): G[NonEmptyVector[B]] =
Foldable[Vector].reduceRightToOption[A, G[Vector[B]]](nel.tail)(a => G.map(f(a))(_ +: Vector.empty)) { (a, lglb) =>
G.map2Eval(f(a), lglb)(_ +: _)
}.map {
case None => G.map(f(nel.head))(NonEmptyVector(_, Vector.empty))
case Some(gtail) => G.map2(f(nel.head), gtail)(NonEmptyVector(_, _))
}.value

override def traverse[G[_], A, B](fa: NonEmptyVector[A])(f: (A) => G[B])(implicit G: Applicative[G]): G[NonEmptyVector[B]] =
G.map2Eval(f(fa.head), Always(Traverse[Vector].traverse(fa.tail)(f)))(NonEmptyVector(_, _)).value

override def foldLeft[A, B](fa: NonEmptyVector[A], b: B)(f: (B, A) => B): B =
Expand Down
25 changes: 24 additions & 1 deletion core/src/main/scala/cats/data/OneAnd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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]]{
Expand Down Expand Up @@ -221,4 +225,23 @@ private[data] trait OneAndLowPriority2 extends OneAndLowPriority1 {
}
}

private[data] trait OneAndLowPriority3 extends OneAndLowPriority2 {
implicit def catsDataNonEmptyTraverseForOneAnd[F[_]](implicit F: Traverse[F], F2: MonadCombine[F]): NonEmptyTraverse[OneAnd[F, ?]] =
new NonEmptyReducible[OneAnd[F, ?], F] with NonEmptyTraverse[OneAnd[F, ?]] {
def nonEmptyTraverse[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))))
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you add the implementation of traverse used in the Traverse instance above here as well?


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
6 changes: 3 additions & 3 deletions core/src/main/scala/cats/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ package object cats {
* encodes pure unary function application.
*/
type Id[A] = A
implicit val catsInstancesForId: Bimonad[Id] with Monad[Id] with Traverse[Id] with Reducible[Id] =
new Bimonad[Id] with Monad[Id] with Traverse[Id] with Reducible[Id] {
implicit val catsInstancesForId: Bimonad[Id] with Monad[Id] with NonEmptyTraverse[Id] =
new Bimonad[Id] with Monad[Id] with NonEmptyTraverse[Id] {
def pure[A](a: A): A = a
def extract[A](a: A): A = a
def flatMap[A, B](a: A)(f: A => B): B = f(a)
Expand All @@ -51,7 +51,7 @@ package object cats {
def foldLeft[A, B](a: A, b: B)(f: (B, A) => B) = f(b, a)
def foldRight[A, B](a: A, lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
f(a, lb)
def traverse[G[_], A, B](a: A)(f: A => G[B])(implicit G: Applicative[G]): G[B] =
def nonEmptyTraverse[G[_], A, B](a: A)(f: A => G[B])(implicit G: Apply[G]): G[B] =
f(a)
override def foldMap[A, B](fa: Id[A])(f: A => B)(implicit B: Monoid[B]): B = f(fa)
override def reduce[A](fa: Id[A])(implicit A: Semigroup[A]): A =
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ trait AllSyntax
with StrongSyntax
with TraverseFilterSyntax
with TraverseSyntax
with NonEmptyTraverseSyntax
with TupleSyntax
with ValidatedSyntax
with VectorSyntax
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/scala/cats/syntax/nonEmptyTraverse.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package cats
package syntax

trait NonEmptyTraverseSyntax extends NonEmptyTraverse.ToNonEmptyTraverseOps
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ package object syntax {
object strong extends StrongSyntax
object monadTrans extends MonadTransSyntax
object traverse extends TraverseSyntax
object nonEmptyTraverse extends NonEmptyTraverseSyntax
object traverseFilter extends TraverseFilterSyntax
object tuple extends TupleSyntax
object validated extends ValidatedSyntax
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala/cats/syntax/reducible.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cats
package syntax


trait ReducibleSyntax extends Reducible.ToReducibleOps {
implicit final def catsSyntaxNestedReducible[F[_]: Reducible, G[_], A](fga: F[G[A]]): NestedReducibleOps[F, G, A] =
new NestedReducibleOps[F, G, A](fga)
Expand All @@ -9,3 +10,4 @@ trait ReducibleSyntax extends Reducible.ToReducibleOps {
final class NestedReducibleOps[F[_], G[_], A](val fga: F[G[A]]) extends AnyVal {
def reduceK(implicit F: Reducible[F], G: SemigroupK[G]): G[A] = F.reduceK(fga)
}

Loading