用语义上等效的猫功能替换 scalaz ListT

Replace scalaz ListT with semantically equivalent cats functionality

cats provide ListT monad transformer so how could we rewrite the following snippet 使用 scalaz ListT 来理解 cats

中语义等效的片段
import scalaz._
import ListT._
import scalaz.std.option._

val seeds: Option[List[String]] = Some(List("apple", "orange", "tomato"))
def grow(seed: String): Option[List[String]] = Some(List(seed.toUpperCase))
def family(seed: String, plant: String): Option[List[(String, String)]] = Some(List(seed -> plant))

(for {
  seed    <- listT(seeds)
  plant   <- listT(grow(seed))
  result  <- listT(family(seed, plant))
} yield result).run

这是我利用 flatMapflatTraverse

的尝试
import cats.implicits._

seeds
  .flatMap {
    _.flatTraverse { seed =>
      grow(seed)
        .flatMap {
          _.flatTraverse { plant =>
            family(seed, plant)
          }
        }
    }
  }

这种重构似乎可以满足类型检查器的要求,但我不确定快乐的编译器是否能确保 100% 的语义等价。

Cats 不提供 ListT,因为它违反了结合性 Monad 定律。参见 Cats FAQ and associated proof using scalaz ListT

根据您建议的 .flatTraverse 以下 ListT 实现仍然通过了所有 cats-core 法则测试(错误?)。

我没有软件验证经验,但您可能会发现成功的测试足以将这 2 种实现视为等效。

ListT 实现

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)

法律测试

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

法律测试结果

- 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