Cats does not provide ListT because it breaks the associativity Monad law. See Cats FAQ and associated proof using scalaz ListT.
Still the following ListT
implementation based on .flatTraverse
as you suggest passes all cats-core law tests (a bug?).
I have no experience with software proving but you might find the successful tests good enough to consider the 2 implementations as equivalent.
ListT implementation
case class ListT[M[_], A](value: M[List[A]])
implicit def listTMonad[M[_]: Monad] = new Monad[ListT[M, *]] {
override def flatMap[A, B](fa: ListT[M, A])(f: A => ListT[M, B]): ListT[M, B] =
ListT(
Monad[M].flatMap[List[A], List[B]](fa.value)(
list => Traverse[List].flatTraverse[M, A, B](list)(a => f(a).value)
)
)
override def pure[A](a: A): ListT[M, A] = ListT(Monad[M].pure(List(a)))
// unsafe impl, can be ignored for this question
override def tailRecM[A, B](a: A)(f: A => ListT[M, Either[A, B]]): ListT[M, B] =
flatMap(f(a)) {
case Right(b) => pure(b)
case Left(nextA) => tailRecM(nextA)(f)
}
}
sbt
name := "listT_tests"
version := "0.1"
scalaVersion := "2.11.12"
scalacOptions += "-Ypartial-unification"
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.0.0",
"org.scalaz" %% "scalaz-core" % "7.2.30",
"org.scalacheck" %% "scalacheck" % "1.14.1" % "test",
"org.scalatest" %% "scalatest" % "2.2.6" % "test",
"org.typelevel" %% "discipline-scalatest" % "1.0.1",
"org.typelevel" %% "discipline-core" % "1.0.2",
"org.typelevel" %% "cats-laws" % "2.0.0" % Test,
"com.github.alexarchambault" %% "scalacheck-shapeless_1.14" % "1.2.3" % Test
)
addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full)
law tests
class TreeLawTests extends AnyFunSpec with Checkers with FunSpecDiscipline {
implicit def listTEq[M[_], A] = Eq.fromUniversalEquals[ListT[M, A]]
checkAll("ListT Monad Laws", MonadTests[ListT[Option, *]].stackUnsafeMonad[Int, Int, String])
}
law tests results
- monad (stack-unsafe).ap consistent with product + map
- monad (stack-unsafe).applicative homomorphism
- monad (stack-unsafe).applicative identity
- monad (stack-unsafe).applicative interchange
- monad (stack-unsafe).applicative map
- monad (stack-unsafe).applicative unit
- monad (stack-unsafe).apply composition
- monad (stack-unsafe).covariant composition
- monad (stack-unsafe).covariant identity
- monad (stack-unsafe).flatMap associativity
- monad (stack-unsafe).flatMap consistent apply
- monad (stack-unsafe).flatMap from tailRecM consistency
- monad (stack-unsafe).invariant composition
- monad (stack-unsafe).invariant identity
- monad (stack-unsafe).map flatMap coherence
- monad (stack-unsafe).map2/map2Eval consistency
- monad (stack-unsafe).map2/product-map consistency
- monad (stack-unsafe).monad left identity
- monad (stack-unsafe).monad right identity
- monad (stack-unsafe).monoidal left identity
- monad (stack-unsafe).monoidal right identity
- monad (stack-unsafe).mproduct consistent flatMap
- monad (stack-unsafe).productL consistent map2
- monad (stack-unsafe).productR consistent map2
- monad (stack-unsafe).semigroupal associativity
- monad (stack-unsafe).tailRecM consistent flatMap