-
-
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 4 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,133 @@ | ||
package cats | ||
|
||
import cats.data.NonEmptyList | ||
import simulacrum.typeclass | ||
|
||
@typeclass trait Traverse1[F[_]] extends Traverse[F] with Reducible[F] { | ||
|
||
def traverse1[G[_]: Apply, A, B](fa: F[A])(f: A => G[B]): G[F[B]] | ||
|
||
def sequence1[G[_]: Apply, A](fga: F[G[A]]): G[F[A]] = | ||
traverse1(fga)(identity) | ||
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. This method is untested. |
||
|
||
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 |
||
|
||
def flatSequence[G[_], A](fgfa: F[G[F[A]]])(implicit G: Apply[G], F: FlatMap[F]): G[F[A]] = | ||
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. Did you mean to call this |
||
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) | ||
|
||
override def sequence[G[_] : Applicative, A](fga: F[G[A]]): G[F[A]] = | ||
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. By overriding |
||
traverse1(fga)(identity) | ||
|
||
|
||
|
||
def traverse1U[A, GB](fa: F[A])(f: A => GB)(implicit G: Unapply[Apply, GB]): G.M[F[G.A]] = | ||
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. all unapplied versions are no longer needed |
||
traverse1(fa)(G.subst.compose(f))(G.TC) | ||
|
||
def sequenceU1[GA](fga: F[GA])(implicit U: Unapply[Apply, GA]): U.M[F[U.A]] = | ||
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. Should this perhaps be |
||
traverse1(fga)(U.subst)(U.TC) | ||
|
||
override def reduceMap[A, B](fa: F[A])(f: (A) => B)(implicit B: Semigroup[B]): B = | ||
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 I don't think it makes any sense for us to override Relevant discussion about |
||
reduceLeft(traverse1[Id, A, B](fa)(f))(B.combine) | ||
|
||
override def map[A, B](fa: F[A])(f: A => B): F[B] = | ||
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. We get this by overriding |
||
traverse1[Id, A, B](fa)(f) | ||
|
||
} | ||
|
||
/** | ||
* This class defines a `Reducible[F]` in terms of a `Foldable[G]` | ||
* together with a `split` method, `F[A]` => `(A, G[A])`. | ||
* | ||
* This class can be used on any type where the first value (`A`) and | ||
* the "rest" of the values (`G[A]`) can be easily found. | ||
*/ | ||
abstract class NonEmptyTraverse1[F[_], G[_]](implicit G: Foldable[G] with Traverse[G]) extends Traverse1[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. Tests? |
||
def split[A](fa: F[A]): (A, G[A]) | ||
|
||
def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B = { | ||
val (a, ga) = split(fa) | ||
G.foldLeft(ga, f(b, a))(f) | ||
} | ||
|
||
def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = | ||
Always(split(fa)).flatMap { case (a, ga) => | ||
f(a, G.foldRight(ga, lb)(f)) | ||
} | ||
|
||
def reduceLeftTo[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): B = { | ||
val (a, ga) = split(fa) | ||
G.foldLeft(ga, f(a))((b, a) => g(b, a)) | ||
} | ||
|
||
def reduceRightTo[A, B](fa: F[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = | ||
Always(split(fa)).flatMap { case (a, ga) => | ||
G.reduceRightToOption(ga)(f)(g).flatMap { | ||
case Some(b) => g(a, Now(b)) | ||
case None => Later(f(a)) | ||
} | ||
} | ||
|
||
override def size[A](fa: F[A]): Long = { | ||
val (_, tail) = split(fa) | ||
1 + G.size(tail) | ||
} | ||
|
||
override def fold[A](fa: F[A])(implicit A: Monoid[A]): A = { | ||
val (a, ga) = split(fa) | ||
A.combine(a, G.fold(ga)) | ||
} | ||
|
||
override def foldM[H[_], A, B](fa: F[A], z: B)(f: (B, A) => H[B])(implicit H: Monad[H]): H[B] = { | ||
val (a, ga) = split(fa) | ||
H.flatMap(f(z, a))(G.foldM(ga, _)(f)) | ||
} | ||
|
||
override def find[A](fa: F[A])(f: A => Boolean): Option[A] = { | ||
val (a, ga) = split(fa) | ||
if (f(a)) Some(a) else G.find(ga)(f) | ||
} | ||
|
||
override def exists[A](fa: F[A])(p: A => Boolean): Boolean = { | ||
val (a, ga) = split(fa) | ||
p(a) || G.exists(ga)(p) | ||
} | ||
|
||
override def forall[A](fa: F[A])(p: A => Boolean): Boolean = { | ||
val (a, ga) = split(fa) | ||
p(a) && G.forall(ga)(p) | ||
} | ||
|
||
override def toList[A](fa: F[A]): List[A] = { | ||
val (a, ga) = split(fa) | ||
a :: G.toList(ga) | ||
} | ||
|
||
override def toNonEmptyList[A](fa: F[A]): NonEmptyList[A] = { | ||
val (a, ga) = split(fa) | ||
NonEmptyList(a, G.toList(ga)) | ||
} | ||
|
||
override def filter_[A](fa: F[A])(p: A => Boolean): List[A] = { | ||
val (a, ga) = split(fa) | ||
val filteredTail = G.filter_(ga)(p) | ||
if (p(a)) a :: filteredTail else filteredTail | ||
} | ||
|
||
override def takeWhile_[A](fa: F[A])(p: A => Boolean): List[A] = { | ||
val (a, ga) = split(fa) | ||
if (p(a)) a :: G.takeWhile_(ga)(p) else Nil | ||
} | ||
|
||
override def dropWhile_[A](fa: F[A])(p: A => Boolean): List[A] = { | ||
val (a, ga) = split(fa) | ||
if (p(a)) G.dropWhile_(ga)(p) else a :: G.toList(ga) | ||
} | ||
|
||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,8 @@ package data | |
|
||
import cats.instances.list._ | ||
import cats.syntax.order._ | ||
import cats.syntax.semigroup._ | ||
import cats.syntax.cartesian._ | ||
|
||
import scala.annotation.tailrec | ||
import scala.collection.immutable.TreeSet | ||
|
@@ -323,9 +325,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 Traverse1[NonEmptyList] with Monad[NonEmptyList] = | ||
new NonEmptyReducible[NonEmptyList, List] with SemigroupK[NonEmptyList] with Comonad[NonEmptyList] | ||
with Traverse[NonEmptyList] with Monad[NonEmptyList] { | ||
with Traverse1[NonEmptyList] with Monad[NonEmptyList] { | ||
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. This one as well, why not |
||
|
||
def combineK[A](a: NonEmptyList[A], b: NonEmptyList[A]): NonEmptyList[A] = | ||
a concat b | ||
|
@@ -352,7 +354,12 @@ 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 traverse1[G[_] : Apply, A, B](as: NonEmptyList[A])(f: A => G[B]): G[NonEmptyList[B]] = { | ||
as.map(a => Apply[G].map(f(a))(NonEmptyList.of(_))) | ||
.reduceLeft((acc, a) => (acc |@| a).map( _ |+| _ )) | ||
} | ||
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. This is not really the best implementation of A better implementation : def traverse1[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
A benchmark comparing your
The difference of just replacing |
||
|
||
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 = | ||
|
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. | ||
*/ | ||
|
@@ -202,10 +206,22 @@ private[data] trait OneAndLowPriority1 extends OneAndLowPriority0 { | |
} | ||
|
||
private[data] trait OneAndLowPriority2 extends OneAndLowPriority1 { | ||
implicit def catsDataTraverseForOneAnd[F[_]](implicit F: Traverse[F]): Traverse[OneAnd[F, ?]] = | ||
new Traverse[OneAnd[F, ?]] { | ||
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 | ||
implicit def catsDataTraverseForOneAnd[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. I'm curious; why doesn't this use 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. Hey, I'm not sure what exactly you mean by this comment, if you could clarify, that'd be great :) 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. @LukaJCB I think that @edmundnoble is asking why the |
||
new Traverse1[OneAnd[F, ?]] { | ||
|
||
override 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)))) | ||
} | ||
|
||
def reduceLeftTo[A, B](fa: OneAnd[F, A])(f: A => B)(g: (B, A) => B): B = { | ||
fa.foldLeft(f(fa.head))(g) | ||
} | ||
|
||
def reduceRightTo[A, B](fa: OneAnd[F, A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = { | ||
fa.foldRight(Always(f(fa.head)))(g) | ||
} | ||
|
||
def foldLeft[A, B](fa: OneAnd[F, A], b: B)(f: (B, A) => B): B = { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,12 @@ | ||
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) | ||
private[syntax] trait ReducibleSyntax1 { | ||
implicit def catsSyntaxUReducible[FA](fa: FA)(implicit U: Unapply[Reducible, FA]): Reducible.Ops[U.M, U.A] = | ||
new Reducible.Ops[U.M, U.A] { | ||
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. we can use a doctest in the type class to cover this. |
||
val self = U.subst(fa) | ||
val typeClassInstance = U.TC | ||
} | ||
} | ||
|
||
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) | ||
} | ||
trait ReducibleSyntax extends Reducible.ToReducibleOps with ReducibleSyntax1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package cats | ||
package syntax | ||
|
||
private[syntax] trait Traverse1Syntax1 { | ||
implicit def catsSyntaxUTraverse1[FA](fa: FA)(implicit U: Unapply[Traverse1, FA]): Traverse1.Ops[U.M, U.A] = | ||
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. We don't need this unapply version anymore. |
||
new Traverse1.Ops[U.M, U.A] { | ||
val self = U.subst(fa) | ||
val typeClassInstance = U.TC | ||
} | ||
} | ||
|
||
trait Traverse1Syntax extends Traverse1.ToTraverse1Ops with Traverse1Syntax1 { | ||
implicit def catsSyntaxNestedTraverse1[F[_]: Traverse1, G[_], A](fga: F[G[A]]): NestedTraverse1Ops[F, G, A] = | ||
new NestedTraverse1Ops[F, G, A](fga) | ||
} | ||
|
||
final class NestedTraverse1Ops[F[_], G[_], A](fga: F[G[A]])(implicit F: Traverse1[F]) { | ||
def reduceK(implicit G: SemigroupK[G]): G[A] = F.reduceK(fga) | ||
} |
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.
Can we also add some brief scaladocs there?