あなたが持っていて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)
アップデート
実際にApplicativeを使用して、上記のコードを簡略化できます。
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(_, _))
}
そのように、リストのリスト、ツリーのリスト、...を合計することもできます。
UPDATE2
質問を明確にすると、上記のUPDATEが正しくないことがわかります。
まず、optionTIsMonoid
リファクタリングされた、は最初の定義と同等ではありません。最初の定義は値をスキップし、2番目の定義は入力リストにaが含まれるとすぐにNone
戻るためです。しかし、その場合、これは!ではありません。確かに、はモノイドの法則を尊重しなければならず、単位元でなければなりません。None
None
Monoid
Monoid[T]
zero
我々が持っている必要があります:
zero |+| Some(a) = Some(a)
Some(a) |+| zero = Some(a)
しかし、 forMonoid[Option[T]]
を使用するApplicative
ための定義を提案したとき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(_, _))
}
この場合、次のようになります(with 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
これは、同一性法則が検証されていることを証明します(結合法則が尊重されていることも検証する必要があります...)。
Monoid[Option[T]]
これで、リストを合計するときに必要な動作に応じて、自由に使用できる2つができました。sをスキップNone
するか、「高速で失敗する」かです。