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 4 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
133 changes: 133 additions & 0 deletions core/src/main/scala/cats/Traverse1.scala
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] {
Copy link
Contributor

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?


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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

@edmundnoble edmundnoble Apr 29, 2017

Choose a reason for hiding this comment

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

Ditto (untested) for this, and flatSequence, sequence, traverse1U, sequence1U in the same class.


def flatSequence[G[_], A](fgfa: F[G[F[A]]])(implicit G: Apply[G], F: FlatMap[F]): G[F[A]] =
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did you mean to call this flatSequence1?

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]] =
Copy link
Collaborator

Choose a reason for hiding this comment

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

By overriding traverse we already get this, not sure if we should explicitly override this?

traverse1(fga)(identity)



def traverse1U[A, GB](fa: F[A])(f: A => GB)(implicit G: Unapply[Apply, GB]): G.M[F[G.A]] =
Copy link
Contributor

Choose a reason for hiding this comment

The 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]] =
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this perhaps be sequence1U?

traverse1(fga)(U.subst)(U.TC)

override def reduceMap[A, B](fa: F[A])(f: (A) => B)(implicit B: Semigroup[B]): B =
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think foldMap1 is implemented in Scalaz using traverse1 because that way Traverse1 implements Foldable1 as well. The primitive operations of Reducible are reduceLeftTo and reduceRightTo.

I don't think it makes any sense for us to override reduceMap here.

Relevant discussion about Traverse implementing Foldable : #107.

reduceLeft(traverse1[Id, A, B](fa)(f))(B.combine)

override def map[A, B](fa: F[A])(f: A => B): F[B] =
Copy link
Collaborator

Choose a reason for hiding this comment

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

We get this by overriding traverse as well.

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] {
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
}



}
13 changes: 10 additions & 3 deletions core/src/main/scala/cats/data/NonEmptyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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] {
Copy link
Contributor

@edmundnoble edmundnoble Apr 29, 2017

Choose a reason for hiding this comment

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

This one as well, why not NonEmptyTraverse1 it up?


def combineK[A](a: NonEmptyList[A], b: NonEmptyList[A]): NonEmptyList[A] =
a concat b
Expand All @@ -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( _ |+| _ ))
}
Copy link
Collaborator

@peterneyens peterneyens May 28, 2017

Choose a reason for hiding this comment

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

This is not really the best implementation of traverse1.

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

Traverse[Vector].traverse also uses prepending (+:), so I think this should work for NonEmptyVector as well. For OneAnd we need to come up with something else if we want to improve that one as well.

A benchmark comparing your traverse1 to mine above and a copy of yours using NonEmptyList(_, Nil) instead of NonEmptyList.of.

[info] Benchmark                          Mode  Cnt      Score     Error  Units
[info] NelTraverseBench.traverse1noOf    thrpt   10  10977.183 ± 240.703  ops/s
[info] NelTraverseBench.traverse1prime   thrpt   10  89787.940 ± 849.006  ops/s
[info] NelTraverseBench.traverse1        thrpt   10   3929.498 ±  72.846  ops/s

The difference of just replacing NonEmptyList.of is already quite big, so I am plugging #1707 here again.


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
24 changes: 20 additions & 4 deletions 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 @@ -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, ?]] =
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm curious; why doesn't this use NonEmptyTraverse1?

Copy link
Member Author

Choose a reason for hiding this comment

The 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 :)

Copy link
Contributor

Choose a reason for hiding this comment

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

@LukaJCB I think that @edmundnoble is asking why the Traverse1 instance for OneAnd isn't using the NonEmptyTraverse1 abstract class that you've submitted with this PR.

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 = {
Expand Down
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 Traverse1[Id] =
new Bimonad[Id] with Monad[Id] with Traverse1[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 traverse1[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 TransLiftSyntax
with TraverseFilterSyntax
with TraverseSyntax
with Traverse1Syntax
with TupleSyntax
with ValidatedSyntax
with VectorSyntax
Expand Down
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 transLift extends TransLiftSyntax
object traverse extends TraverseSyntax
object traverse1 extends Traverse1Syntax
object traverseFilter extends TraverseFilterSyntax
object tuple extends TupleSyntax
object validated extends ValidatedSyntax
Expand Down
13 changes: 7 additions & 6 deletions core/src/main/scala/cats/syntax/reducible.scala
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] {
Copy link
Contributor

Choose a reason for hiding this comment

The 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
19 changes: 19 additions & 0 deletions core/src/main/scala/cats/syntax/traverse1.scala
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] =
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
}
2 changes: 2 additions & 0 deletions docs/src/main/tut/typeclasses/typeclasses.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ type class instances easy.
Foldable -> Traverse
Functor -> Traverse
Foldable -> Reducible
Reducible -> Traverse1
Traverse -> Traverse1
}
)

Expand Down
73 changes: 73 additions & 0 deletions laws/src/main/scala/cats/laws/Traverse1Laws.scala
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] {
Copy link
Contributor

Choose a reason for hiding this comment

The 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 Traverse1 instance and one for a data type that uses the NonEmptyTraverse1.

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 }
}
Loading