2011-11-16 14 views
9

Tengo una lista [Opción [Int]] y quiero resumirla utilizando funtores aplicativos. De [1] entiendo que debe ser algo como lo siguienteSumar una lista de opciones con los funcionativos aplicables

import scalaz._ 
import Scalaz._ 

List(1,2,3).map(some(_)).foldLeft(some(0))({ 
    case (acc,value) => (acc <|*|> value){_+_} 
}) 

sin embargo, simplemente no soy capaz de averiguar la forma correcta de escribir esto. Me alegraría si alguien pudiera ayudarme con esto.

Muchas gracias

[1] How to combine Option values in Scala?

Editar

Gracias por todas las grandes respuestas.

Si hay Ninguno en la lista, quiero que devuelva Ninguno. Estoy tratando de reemplazar Null/Exception con Option/Either y ver si puedo producir algún código utilizable.

Alguna función llenará mi lista y quiero procesarla aún más fácilmente sin verificar si uno de los elementos fue Ninguno. Debería funcionar de forma similar a las Excepciones, donde no tengo que verificarlo en mi función, sino dejar que la persona que llama se encargue de ello.

+0

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

+0

Hola Andreas. Los fragmentos de código como el tuyo es lo que necesito. –

Respuesta

9

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".

+0

Justo después de publicar esto, me doy cuenta que no estoy realmente responder a la pregunta, ya que no estoy usando cualquier aplicativo. Simplemente considere esto como una de las numerosas alternativas, ... – Eric

+0

No entiendo cómo funciona ... ¿por qué no hay '+' en ninguna parte? – Owen

+1

'.foldMap (identidad)' se puede reemplazar por '.asMA.sum'. – missingfaktor

5

Una opción sería para secuenciar toda la lista primero, y luego doblarla como normal:

val a: List[Option[Int]] = List(1, 2, 3) map (Some(_)) 
a.sequence map (_.foldLeft(0)(_+_)) 
+2

O, de hecho, simplemente a.sequence mapa {} _.sum – Submonoid

14

Usted realmente no necesita Scalaz para esto. Puede simplemente aplanar la lista, que lo convertirá en List[Int], eliminando los elementos que fueron None. A continuación, puede reducirlo:

List(Some(1), None, Some(2), Some(3), None).flatten.reduce(_ + _) //returns 6: Int 
+0

había interpretado como queriendo hacer que el resultado 'None' si alguno era' None', pero ahora que lo mencionas, no estoy tan seguro de que estaba derecho ... – Owen

+0

Muy buen punto, supongo que el OP debe especificar si quiere que la suma a fallar si al menos un artículo es Ninguno, o para resumir sobre los elementos que tienen valores, haciendo caso omiso de las nonas. –

+6

O '.flatten.sum'. – missingfaktor

6
scala> List(1, 2, 3).map(some).foldLeft(0 some) { 
    | case (r, c) => (r |@| c)(_ + _) 
    | } 
res180: Option[Int] = Some(6) 
0

Con ApplicativeBuilder de Scalaz habría otra opción.

import scalaz._ 
import Scalaz._ 

List(1,2,3).map(_.some).foldl1((acc,v) => (acc |@| v) {_+_}) join 
0

Encontrado hace esto en alguna parte de un tiempo, no se puede encontrar la fuente más, pero ha estado trabajando para mí

def sumOpt1(lst: List[Option[Int]]): Option[Int] = { 
    lst.foldLeft(Option.empty[Int]) { 
     case (prev, elem) => 
     (prev, elem) match { 
      case (None, None) => None 
      case (None, Some(el)) => Some(el) 
      case (Some(p), None) => Some(p) 
      case (Some(p), Some(el)) => Some(p + el) 
     } 
    } 
    } 

o

def sumOpt2(lst: List[Option[Int]]): Option[Int] = { 
    lst.foldLeft(Option.empty[Int]) { 
     case (prev, elem) => 
     (prev, elem) match { 
      case (None, None) => None 
      case (p, el) => Some(p.getOrElse(0) + el.getOrElse(0)) 
     } 
    } 
    } 

o

def sumOpt3(lst: List[Option[Int]]): Option[Int] = { 
    lst.foldLeft(Option.empty[Int]) { 
     case (prev, elem) => 
     (prev, elem) match { 
      case (None, el) => el 
      case (p, None) => p 
      case (Some(p), Some(el)) => Some(p + el) 
     } 
    } 
    } 
Cuestiones relacionadas