首页 > 解决方案 > 为什么具有 Monad 实例的类型的半群不组合?

问题描述

我正在尝试围绕 Cats 中的 Semigroupals。以下是 Underscore 的“Scala with Cats”中的陈述。

cats.Semigroupal是一个允许我们组合上下文的类型类

trait Semigroupal[F[_]] {
  def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
}

参数fafb是相互独立的:我们可以在将它们传递给之前以任一顺序计算它们product。这与 形成对比flatMap,后者对其参数施加了严格的顺序。

所以基本上,我们也应该能够结合两个Either上下文,但这似乎不起作用:

import cats.instances.either._ 

type ErrorOr[A] = Either[Vector[String], A]

Semigroupal[ErrorOr].product(Left(Vector("Error 1")), Left(Vector("Error 2")))

// res3: ErrorOr[Tuple2[Nothing, Nothing]] = Left(Vector("Error 1"))

如果 semigroupal 的 USP 是急切地执行独立操作,则必须在传递给之前对两者进行评估product,但我们无法获得组合结果。

我们可能期望product应用于Either累积错误而不是快速失败。同样,也许令人惊讶的是,我们发现product实现了与flatMap.

这不是与最初的前提相反,即有一种替代方法可以组合任何相同类型的上下文吗?

map为了确保语义一致,Cats' Monad(它扩展了 Semigroupal)根据和提供了产品的标准定义flatMap

为什么要product根据mapand来实现flatMap?这里指的是什么语义?

那么,为什么要打扰 Semigroupal 呢?答案是我们可以创建有用的数据类型,这些数据类型具有 Semigroupal(和 Applicative)的实例,但没有 Monad。这使我们能够以不同的方式实现产品。

这甚至意味着什么?

不幸的是,这本书没有详细介绍这些前提!网上也找不到资源。谁能解释一下?TIA。

标签: scalascala-cats

解决方案


所以基本上,我们也应该能够组合两个 Either 上下文,但这似乎不起作用:

它起作用了,你可以看到结果是一个有效的结果,它类型检查。

Semigrupal只是暗示给定 anF[A]和 aF[B]它会产生 anF[(A, B)]它并不意味着它能够独立评估两者。它可能会,但也可能不会。与Monad相反,它确实暗示它需要在F[A]之前进行评估,因为要评估F[B]它需要A

这不是与原来的前提相反,即有一种替代方法可以组合任何相同类型的上下文吗?

并不是真正的不同方法Monad[F] <: Semigroupal[F],因为您始终可以调用product任何Monad用Semigroupal实现一个函数只是意味着它对更多类型开放,但它不会改变每种类型的行为。

为什么要用 map 和 flatMap 来实现 product?这里指的是什么语义?

TL;博士; 一致性:

// https://github.com/typelevel/cats/blob/54b3c2a06ff4b31f3c5f84692b1a8a3fbe5ad310/laws/src/main/scala/cats/laws/FlatMapLaws.scala#L18

def flatMapConsistentApply[A, B](fa: F[A], fab: F[A => B]): IsEq[F[B]] =
  fab.ap(fa) <-> fab.flatMap(f => fa.map(f))

上述定律意味着,对于 anyF[A]和 for any F[A => B],只要存在Monad (实际上是FlatMapFfab.ap(fa)fab.flatMap(f => fa.map(f))

现在,为什么?多种原因:

  • 最常见的是最不意外原则,如果我有一堆 any 并将它们传递给一个通用函数,无论它是否需要MonadApplicative我都希望它快速失败,因为这是Either的行为。
  • Liskov,假设我有两个函数fand gf期望ApplicativeMonad,如果g在后台调用我希望调用两者返回相同的结果。gf
  • 任何Monad都必须是Applicative ,但是Either的累积Applicative版本需要 的Semigroup,而Monad实例不需要。Left

这甚至意味着什么?

这意味着我们可以定义另一种类型(例如Validated,它只满足Applicative定律而不满足Monad定律,因此它可以实现的累积版本ap


额外的好处是,因为有一个类型是Monad但可以实现一个不需要排序的Applicative的情况很常见。的维护者创建了Parallel来代表它。

因此,您可以直接在Eithers上使用,而不是将您的Eithers转换为Validateds来组合它们。mapNparMapN


推荐阅读