如果你有Option[T]并且如果有一个Monoidfor 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]]使用Applicativefor的定义时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(_, _))
}
在这种情况下,我们将(使用Tas 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]]我们可以随意使用,这取决于我们在对列表求和时想要的行为:跳过Nones 或“快速失败”。