You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

类型构造器是Monad还是拥有Monad?多实例及Monoid、Applicative相关问题

同一类型构造器能否拥有多个Monad实例?

咱先直接给结论:完全可以! 像Scala/Scalaz、Haskell这类函数式语言/库的类型系统,并没有限制为同一个类型构造器只能定义一个Monad实例——只要每个实例都严格遵守Monad的三条核心定律(左单位元、右单位元、结合律),就都是合法的。

有实际意义的示例:Left-biased 的 Either Monad

最常见的实际场景就是Either类型构造器。Scala标准库自带的Either Monad是Right-biased的:Right代表成功值,Left代表错误,flatMap会自动短路到第一个Left。但在某些场景下,我们可能需要反过来——用Left代表成功,Right代表错误,这时候一个Left-biased的Monad实例就会非常顺手。

比如处理某个第三方API,它返回的Either[SuccessData, ErrorInfo]里,Left才是正常结果,Right是错误。这时候用自定义的Left-biased Monad,就能用for推导式流畅处理,不用每次都手动模式匹配。

Scala代码示例(用Cats库实现):

import cats.Monad
import cats.syntax.all._

// 自定义Left-biased的Either Monad实例
implicit def leftBiasedEitherMonad[E]: Monad[Either[*, E]] = new Monad[Either[*, E]] {
  override def pure[A](x: A): Either[A, E] = Left(x)
  
  override def flatMap[A, B](fa: Either[A, E])(f: A => Either[B, E]): Either[B, E] = fa match {
    case Left(success) => f(success) // 正常流转成功值
    case Right(error) => Right(error) // 遇到错误直接短路
  }
  
  override def tailRecM[A, B](a: A)(f: A => Either[Either[A, E], B]): Either[B, E] = f(a) match {
    case Left(Left(nextSuccess)) => tailRecM(nextSuccess)(f)
    case Left(Right(error)) => Right(error)
    case Right(result) => Left(result)
  }
}

// 模拟数据类
case class User(name: String, age: Int)
case class UserProfile(name: String, desc: String)

// 用for推导式处理Left为成功的场景
val successCase = for {
  userData <- Left(User("Alice", 30)): Either[User, String]
  processed <- Left(UserProfile(userData.name, s"${userData.age}岁")): Either[UserProfile, String]
} yield processed
// 结果:Left(UserProfile("Alice", "30岁"))

// 遇到Right错误时自动短路
val errorCase = for {
  userData <- Right("用户不存在"): Either[User, String]
  processed <- Left(UserProfile("Bob", "25岁")): Either[UserProfile, String]
} yield processed
// 结果:Right("用户不存在")

人工构造的示例:只处理第一个元素的List Monad

为了更直观地展示“同一类型构造器可以有多个Monad”,咱可以人工构造一个和标准List Monad行为完全不同的实例:这个Monad的flatMap只会处理列表的第一个元素,忽略后续所有元素,同时严格遵守Monad定律。

Scala代码示例:

import cats.Monad
import cats.syntax.all._

// 自定义List Monad:只处理第一个元素
implicit def firstElementListMonad: Monad[List] = new Monad[List] {
  override def pure[A](x: A): List[A] = List(x)
  
  override def flatMap[A, B](fa: List[A])(f: A => List[B]): List[B] = fa.headOption match {
    case Some(first) => f(first) // 仅处理第一个元素
    case None => Nil // 空列表直接返回空
  }
  
  override def tailRecM[A, B](a: A)(f: A => List[Either[A, B]]): List[B] = f(a).headOption match {
    case Some(Left(nextA)) => tailRecM(nextA)(f)
    case Some(Right(result)) => List(result)
    case None => Nil
  }
}

// 使用这个自定义Monad
val demo = for {
  num <- List(1, 2, 3, 4)
  doubled <- List(num * 2, num * 3)
} yield doubled
// 结果:List(2, 3) —— 只处理了第一个元素1,忽略了2、3、4

验证一下定律:

  • 左单位元:flatMap(pure(x))(f) = f(x),符合,因为pure(x)是List(x),head就是x,flatMap返回f(x)
  • 右单位元:flatMap(fa)(pure) = pure(fa.head),符合该Monad“只保留第一个元素结果”的语义
  • 结合律:flatMap(flatMap(fa)(f))(g) = flatMap(fa)(a => flatMap(f(a))(g)),所有操作仅针对第一个元素,自然成立

其他代数结构的类似情况

不止Monad,像Monoid、Applicative这类代数结构,也普遍存在“同一类型/类型构造器有多个合法实例”的情况:

Monoid的例子

Monoid是带单位元的结合二元运算,同一类型可以有多种完全不同的Monoid:

  • Int可以有加法Monoid(单位元0,运算_+_)、乘法Monoid(单位元1,运算_*_)、最大值Monoid(单位元Int.MinValue,运算math.max)等。
import cats.Monoid

// 加法Monoid
val addMonoid: Monoid[Int] = Monoid.instance(0, _ + _)
println(addMonoid.combine(3, 5)) // 8

// 乘法Monoid
val multiplyMonoid: Monoid[Int] = Monoid.instance(1, _ * _)
println(multiplyMonoid.combine(3, 5)) // 15

// 最大值Monoid
val maxMonoid: Monoid[Int] = Monoid.instance(Int.MinValue, math.max)
println(maxMonoid.combine(3, 5)) //5

Applicative的例子

Applicative是Monad的弱化版本,同样可以有多个实例。比如List类型构造器:

  • 标准Applicative:ap操作会把每个函数应用到所有元素,结果是所有可能的组合(和标准Monad的flatMap语义一致)
  • ZipList Applicative:ap操作会把函数列表和元素列表按位置拉链,每个函数只应用到对应位置的元素
import cats.Applicative
import cats.data.ZipList
import cats.syntax.all._

// 标准List Applicative
val standardApp = Applicative[List].pure(2) |@| Applicative[List].pure(3) map (_ + _)
// 结果:List(5)

// ZipList Applicative
val zipApp = ZipList(List(1, 2, 3)) |@| ZipList(List(10, 20, 30)) map (_ + _)
// 结果:ZipList(List(11, 22, 33))

内容的提问来源于stack exchange,提问作者Sebastien Lorber

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:09:57