Skip to content

Commit

Permalink
Merge pull request #502 from non/traverse_fold
Browse files Browse the repository at this point in the history
#500 add derived foldMap from traverse law
  • Loading branch information
non committed Sep 15, 2015
2 parents dee1d68 + 01d807b commit c63e0a5
Show file tree
Hide file tree
Showing 9 changed files with 30 additions and 16 deletions.
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/data/Const.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ sealed abstract class ConstInstances0 extends ConstInstances1 {
Const.empty

def ap[A, B](fa: Const[C, A])(f: Const[C, A => B]): Const[C, B] =
fa.retag[B] combine f.retag[B]
f.retag[B] combine fa.retag[B]
}
}

Expand Down
3 changes: 1 addition & 2 deletions core/src/main/scala/cats/std/stream.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ trait StreamInstances {
if (s.isEmpty) lb else f(s.head, Eval.defer(foldRight(s.tail, lb)(f)))
}

def traverse[G[_]: Applicative, A, B](fa: Stream[A])(f: A => G[B]): G[Stream[B]] = {
val G = Applicative[G]
def traverse[G[_], A, B](fa: Stream[A])(f: A => G[B])(implicit G: Applicative[G]): G[Stream[B]] = {
def init: G[Stream[B]] = G.pure(Stream.empty[B])

// We use foldRight to avoid possible stack overflows. Since
Expand Down
9 changes: 2 additions & 7 deletions core/src/main/scala/cats/std/vector.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package cats
package std

import scala.collection.immutable.VectorBuilder

trait VectorInstances {
implicit val vectorInstance: Traverse[Vector] with MonadCombine[Vector] =
new Traverse[Vector] with MonadCombine[Vector] {
Expand Down Expand Up @@ -31,11 +29,8 @@ trait VectorInstances {
Eval.defer(loop(0))
}

def traverse[G[_]: Applicative, A, B](fa: Vector[A])(f: A => G[B]): G[Vector[B]] = {
val G = Applicative[G]
val gba = G.pure(Vector.empty[B])
fa.foldLeft(gba)((buf, a) => G.map2(buf, f(a))(_ :+ _))
}
def traverse[G[_], A, B](fa: Vector[A])(f: A => G[B])(implicit G: Applicative[G]): G[Vector[B]] =
fa.foldLeft(G.pure(Vector.empty[B]))((buf, a) => G.map2(buf, f(a))(_ :+ _))

override def exists[A](fa: Vector[A])(p: A => Boolean): Boolean =
fa.exists(p)
Expand Down
11 changes: 11 additions & 0 deletions laws/src/main/scala/cats/laws/TraverseLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package laws

import cats.Id
import cats.arrow.Compose
import cats.data.Const
import cats.syntax.traverse._
import cats.syntax.foldable._

trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] {
implicit override def F: Traverse[F]
Expand Down Expand Up @@ -48,6 +50,15 @@ trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] {
val rhs: MN[F[B]] = (fa.traverse(f), fa.traverse(g))
lhs <-> rhs
}

def foldMapDerived[A, B](
fa: F[A],
f: A => B
)(implicit B: Monoid[B]): IsEq[B] = {
val lhs: B = fa.traverse[Const[B, ?], B](a => Const(f(a))).getConst
val rhs: B = fa.foldMap(f)
lhs <-> rhs
}
}

object TraverseLaws {
Expand Down
3 changes: 2 additions & 1 deletion laws/src/main/scala/cats/laws/discipline/TraverseTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] {
def props: Seq[(String, Prop)] = Seq(
"traverse identity" -> forAll(laws.traverseIdentity[A, C] _),
"traverse sequential composition" -> forAll(laws.traverseSequentialComposition[A, B, C, X, Y] _),
"traverse parallel composition" -> forAll(laws.traverseParallelComposition[A, B, X, Y] _)
"traverse parallel composition" -> forAll(laws.traverseParallelComposition[A, B, X, Y] _),
"traverse derive foldMap" -> forAll(laws.foldMapDerived[A, M] _)
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/src/test/scala/cats/tests/ListTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ListTests extends CatsSuite with GeneratorDrivenPropertyChecks {
checkAll("List[Int]", MonadCombineTests[List].monadCombine[Int, Int, Int])
checkAll("MonadCombine[List]", SerializableTests.serializable(MonadCombine[List]))

checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, Int, Option, Option])
checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, List[Int], Option, Option])
checkAll("Traverse[List]", SerializableTests.serializable(Traverse[List]))

test("nel => list => nel returns original nel")(
Expand Down
8 changes: 8 additions & 0 deletions tests/src/test/scala/cats/tests/RegressionTests.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cats
package tests

import cats.data.Const

import scala.collection.mutable

class RegressionTests extends CatsSuite {
Expand Down Expand Up @@ -75,4 +77,10 @@ class RegressionTests extends CatsSuite {
)((_: Unit, _: Unit, _: Unit) => ()).run("")._2
oneTwoThree should === ("123")
}

test("#500: foldMap - traverse consistency") {
assert(
List(1,2,3).traverseU(i => Const(List(i))).getConst == List(1,2,3).foldMap(List(_))
)
}
}
4 changes: 2 additions & 2 deletions tests/src/test/scala/cats/tests/StreamTests.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats
package tests

import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests}
import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests, TraverseTests}

class StreamTests extends CatsSuite {
checkAll("Stream[Int]", CoflatMapTests[Stream].coflatMap[Int, Int, Int])
Expand All @@ -10,6 +10,6 @@ class StreamTests extends CatsSuite {
checkAll("Stream[Int]", MonadCombineTests[Stream].monadCombine[Int, Int, Int])
checkAll("MonadCombine[Stream]", SerializableTests.serializable(MonadCombine[Stream]))

checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, Int, Option, Option])
checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, List[Int], Option, Option])
checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream]))
}
4 changes: 2 additions & 2 deletions tests/src/test/scala/cats/tests/VectorTests.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package cats
package tests

import cats.laws.discipline.{TraverseTests, MonadCombineTests, SerializableTests}
import cats.laws.discipline.{MonadCombineTests, SerializableTests, TraverseTests}

class VectorTests extends CatsSuite {
checkAll("Vector[Int]", MonadCombineTests[Vector].monadCombine[Int, Int, Int])
checkAll("MonadCombine[Vector]", SerializableTests.serializable(MonadCombine[Vector]))

checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, Int, Option, Option])
checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, List[Int], Option, Option])
checkAll("Traverse[Vector]", SerializableTests.serializable(Traverse[Vector]))
}

0 comments on commit c63e0a5

Please sign in to comment.