Si tiene Option[T]
y si hay un Monoid
para T
, entonces hay una 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
}
}
Una vez que están equipados con esto, sólo puede use sum
(mejor que foldMap(identity)
, según lo sugerido por @missingfaktor):
List(Some(1), None, Some(2), Some(3), None).asMA.sum === Some(6)
ACTUALIZACIÓN
De hecho, podemos utilizar aplicativos para simplificar el código anterior:
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(_, _))
}
que me hace pensar que podemos tal vez incluso generalizar aún más a:
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(_, _))
}
Al igual que incluso podría sumar listas de listas, listas de árboles, ...
Update2
La pregunta aclaración me hace comprender que la ACTUALIZACIÓN anterior es incorrecta!
En primer lugar optionTIsMonoid
, como refactorizado, no es equivalente a la primera definición, ya que la primera definición se saltará None
valores mientras que el segundo volverá None
tan pronto como hay un None
en la lista de entrada. ¡Pero en ese caso, esto no es un Monoid
! De hecho, un Monoid[T]
deben respetar las leyes monoid y zero
debe ser un elemento identity.
Debemos tener:
zero |+| Some(a) = Some(a)
Some(a) |+| zero = Some(a)
Pero cuando propuse la definición para el Monoid[Option[T]]
utilizando el Applicative
para Option
, este no fue el caso:
None |+| Some(a) = None
None |+| None = None
=> zero |+| a != a
Some(a) |+| None = zero
None |+| None = zero
=> a |+| zero != a
La solución no es difícil, nos necesitará cambiar la definición de 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(_, _))
}
En este caso vamos a tener (con T
como 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
lo que demuestra que se verifica la ley de identidad (que también debe verificar que se respete la ley associative, ...).
Ahora tenemos 2 Monoid[Option[T]]
que podemos usar a voluntad, dependiendo del comportamiento que queremos al sumar la lista: omitiendo None
s o "fallando rápido".
Hola Manuel! Si la parte muy importante siempre es cómo manejar Ninguno: Ignorar o fracasar rápido ver aquí un ejemplo relacionado: https://gist.github.com/970717 – AndreasScheinert
Hola Andreas. Los fragmentos de código como el tuyo es lo que necesito. –