如果你有Option[T]
并且如果有一个Monoid
for T
,那么就有一个Monoid[Option[T]]
:
implicit def optionTIsMonoid[T : Monoid]: Monoid[Option[T]] = new Monoid[Option[T]] {
val monoid = implicitly[Monoid[T]]
val zero = None
def append(o1: Option[T], o2: =>Option[T]) = (o1, o2) match {
case (Some(a), Some(b)) => Some(monoid.append(a, b))
case (Some(a), _) => o1
case (_, Some(b)) => o2
case _ => zero
}
}
一旦你装备了这个,你就可以使用sum
(比foldMap(identity)
@missingfaktor建议的更好):
List(Some(1), None, Some(2), Some(3), None).asMA.sum === Some(6)
更新
我们实际上可以使用应用程序来简化上面的代码:
implicit def optionTIsMonoid[T : Monoid]: Monoid[Option[T]] = new Monoid[Option[T]] {
val monoid = implicitly[Monoid[T]]
val zero = None
def append(o1: Option[T], o2: =>Option[T]) = (o1 |@| o2)(monoid.append(_, _))
}
这让我认为我们甚至可以进一步概括为:
implicit def applicativeOfMonoidIsMonoid[F[_] : Applicative, T : Monoid]: Monoid[F[T]] =
new Monoid[F[T]] {
val applic = implicitly[Applicative[F]]
val monoid = implicitly[Monoid[T]]
val zero = applic.point(monoid.zero)
def append(o1: F[T], o2: =>F[T]) = (o1 |@| o2)(monoid.append(_, _))
}
像这样,您甚至可以对列表列表、树列表进行求和,...
更新2
问题澄清让我意识到上面的UPDATE是不正确的!
首先optionTIsMonoid
,经过重构,不等同于第一个定义,因为第一个定义将跳过None
值,而第二个定义将在输入列表中None
有 a 时立即返回。None
但在那种情况下,这不是一个Monoid
!确实,aMonoid[T]
必须尊重 Monoid 定律,并且zero
必须是恒等元素。
我们本应该:
zero |+| Some(a) = Some(a)
Some(a) |+| zero = Some(a)
但是当我提出Monoid[Option[T]]
使用Applicative
for的定义时Option
,情况并非如此:
None |+| Some(a) = None
None |+| None = None
=> zero |+| a != a
Some(a) |+| None = zero
None |+| None = zero
=> a |+| zero != a
修复并不难,我们需要更改 的定义zero
:
// the definition is renamed for clarity
implicit def optionTIsFailFastMonoid[T : Monoid]: Monoid[Option[T]] =
new Monoid[Option[T]] {
monoid = implicitly[Monoid[T]]
val zero = Some(monoid.zero)
append(o1: Option[T], o2: =>Option[T]) = (o1 |@| o2)(monoid.append(_, _))
}
在这种情况下,我们将(使用T
as Int
):
Some(0) |+| Some(i) = Some(i)
Some(0) |+| None = None
=> zero |+| a = a
Some(i) |+| Some(0) = Some(i)
None |+| Some(0) = None
=> a |+| zero = zero
这证明了恒等律得到验证(我们还应该验证结合律得到尊重,......)。
现在我们有 2 个Monoid[Option[T]]
我们可以随意使用,这取决于我们在对列表求和时想要的行为:跳过None
s 或“快速失败”。