Если у вас есть Option[T]
и если есть Monoid
для 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)
UPDATE
На самом деле мы можем использовать аппликативы для упрощения кода выше:
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
Уточнение вопроса заставляет меня понять, что ОБНОВЛЕНИЕ выше неверно!
Прежде всего, optionTIsMonoid
, в пересмотренном виде, не эквивалентен первому определению, поскольку первое определение пропустит значения None
, а второе вернет None
, как только в 10 * будет * список ввода. Но в этом случае это не Monoid
! Действительно, Monoid[T]
должен уважать законы Моноида, а zero
должен быть элементом идентичности .
У нас должно быть:
zero |+| Some(a) = Some(a)
Some(a) |+| zero = Some(a)
Но когда я предложил определение для Monoid[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(_, _))
}
В этом случае мы будем иметь (с T
как 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 или «быстрый провал».