higherkindness / droste

recursion schemes for cats; to iterate is human, to recurse, divine

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

DefaultTraverse doesn't pass all Traverse laws

ceedubs opened this issue · comments

It's possible that I'm doing something silly here, but I thought that I would report in case that's not what's happening. Either way, it would probably be good for droste to be doing more law-checking in tests.

Consider the following simple data type:

import cats.implicits._
import qq.droste.util.DefaultTraverse
import cats.{Applicative, Eq, Traverse}
import org.scalacheck.Arbitrary

final case class Pair[A](l: A, r: A)

object Pair {

  implicit val traversePair: Traverse[Pair] = new DefaultTraverse[Pair] {
    def traverse[G[_], A, B](fa: Pair[A])(f: A => G[B])(implicit G: Applicative[G]): G[Pair[B]] =
      G.map2(f(fa.l), f(fa.r))(Pair(_, _))
  }

  implicit def eqPair[A](implicit eqA: Eq[A]): Eq[Pair[A]] = Eq.instance((p1, p2) =>
    p1.l === p2.l && p1.r === p2.r
  )
}

Now run the following test:

import cats.tests.CatsSuite
import cats.laws.discipline.TraverseTests

class DefaultTraverseTests extends CatsSuite {
  implicit def arbPair[A](implicit arbA: Arbitrary[A]): Arbitrary[Pair[A]] = Arbitrary(
    arbA.arbitrary.flatMap(a1 => arbA.arbitrary.map(a2 => Pair(a1, a2))))

  checkAll("Pair", TraverseTests[Pair].traverse[Int, Int, Int, Set[Int], Option, Option])
}

The collectFirstSome and foldM identity laws consistently fail. Here is some example output:

- Pair.traverse.collectFirstSome reference *** FAILED ***
  GeneratorDrivenPropertyCheckFailedException was thrown during property evaluation.
   (Discipline.scala:14)
    Falsified after 10 successful property evaluations.
    Location: (Discipline.scala:14)
    Occurred when passed generated values (
      arg0 = Pair(1,1284725810),
      arg1 = org.scalacheck.GenArities$$Lambda$11995/1290092155@1195eb5b
    )
    Label of failing property:
      Expected: Some(Set())
  Received: Some(Set(-1))
- Pair.traverse.foldM identity *** FAILED ***
  GeneratorDrivenPropertyCheckFailedException was thrown during property evaluation.
   (Discipline.scala:14)
    Falsified after 5 successful property evaluations.
    Location: (Discipline.scala:14)
    Occurred when passed generated values (
      arg0 = Pair(0,2147483647),
      arg1 = Set(2061683075),
      arg2 = org.scalacheck.GenArities$$Lambda$12122/454406410@5b35771c
    )
    Label of failing property:
      Expected: Set()
  Received: Set(-225533737)

Both of these are ultimately implemented in terms of foldRight, so I suspect that there is an issue with the DefaultTraverse.foldRight implementation.

I believe this issue was resolved in #81, as the test has been ported over and is now passing. Happy to reopen this issue if we need to.