2010-04-26 10 views
26

Je veux être en mesure d'appliquer une opération f: (T,T) => T à Option[T] valeurs dans Scala. Je veux que le résultat soit None si l'une des deux valeurs est None.Comment combiner les valeurs d'options dans Scala?

Plus précisément, je veux savoir s'il est un chemin plus court pour faire ce qui suit:

def opt_apply[T](f: (T,T) => V, x: Option[T], y: Option[T]): Option[T] = { 
    (x,y) match { 
    case (Some(u),Some(v)) => Some(f(u,v)) 
    case _ => None 
    } 
} 

Je tryied (x zip y) map {case (u,v) => f(u,v)} mais le résultat est un Iterator[T] pas Option[T].

Toute aide sera appréciée. Merci.

Répondre

28
scala> val (x, y) = (Some(4), Some(9)) 
x: Some[Int] = Some(4) 
y: Some[Int] = Some(9) 

scala> def f(x: Int, y: Int) = Math.max(x, y) 
f: (x: Int,y: Int)Int 

scala> for { x0 <- x; y0 <- y } yield f(x0, y0) 
res26: Option[Int] = Some(9) 

scala> val x = None 
x: None.type = None 

scala> for { x0 <- x; y0 <- y } yield f(x0, y0) 
res27: Option[Int] = None 
+0

errrr, cela devrait être mis à jour à droite http://docs.scala-lang.org/style/control-structures.html comme il se doit pour {x0 <- x; y0 <- y)}, je pense, n'est-ce pas? –

+0

@DeanHiller, hmm ouais. Le guide de style n'existait pas au moment où je pense. :) – missingfaktor

17

réponse de @RahulG exploite le fait que Option est une monade (même si il n'y a pas de type pour représenter ce dans la bibliothèque Scala). Le compilateur élargit la compréhension for à ce qui suit:

def a: Option[Int] 
def b: Option[Int] 
val calc: Option[Int] = a flatMap {aa => b map {bb => aa + bb}} 

Vous pouvez également le traiter comme un foncteur applicatif, avec l'aide de Scalaz:

import scalaz._ 
import Scalaz._ 

def a: Option[Int] 
def b: Option[Int] 
val calc: Option[Int] = (a ⊛ b) {_ + _} 

Une différence essentielle est que dans le calcul monadique, un échec (c'est-à-dire None) du calcul a court-circuite l'évaluation. Dans le style applicatif, les deux a et b sont évalués, et si les deux sont Some s, la fonction pure est appelée. Vous pouvez également voir que dans le calcul monadique, la valeur aa aurait pu être utilisée dans le calcul b; dans la version applicative, b ne peut pas dépendre du résultat de a.

+0

L'équivalent ASCII '<|*|>' pour cette méthode? –

+1

'<*>' vous permet de fournir la fonction 'pure', dans ce cas '(a, b) => a + b'. '<|*|>' est une commodité pour utiliser 'Tuple2.apply' comme fonction pure. '⊛' est en fait un peu plus général que arity-2, vous pouvez écrire' (a ⊛ b a ⊛ b) {_ + _ + _ + _} '. C'est un peu expérimental, et en tant que tel, n'a pas encore d'alias ASCII. – retronym

+0

Typo, je voulais dire: '(a ⊛ b ⊛ a ⊛ b) {_ + _ + _ + _}' – retronym

3

J'ai une version légèrement plus ancienne de scalaz que retronym mais les œuvres suivantes pour moi à titre d'exemple et est généralisable pour le cas où vous avez 3 types T, U, V et pas seulement un:

def main(args: Array[String]) { 
    import scalaz._ 
    import Scalaz._ 

    val opt1 = some(4.0) //Option[Double] 
    val opt2 = some(3) //Option[Int] 

    val f: (Double, Int) => String = (d, i) => "[%d and %.2f]".format(i, d) 

    val res = (opt1 <|*|> opt2).map(f.tupled) 
    println(res) //Some([3 and 4.00]) 
} 

Je puis ajouter:

val opt3 = none[Int] 
val res2 = (opt1 <|*|> opt3).map(f.tupled) 
println(res2) //None 
+1

Remplacez' <|*|> 'par' <*> 'pour éviter la création de l'uplet temporaire et utilisez' f' directement. – retronym

+0

Ne fonctionne pas avec différents types paramétriques, je pense –

+0

Oups, je voulais dire '<**>' – retronym

1

Vous pouvez utiliser pour compréhensions:

def opt_apply[T](f: (T,T) => T, x: Option[T], y: Option[T]): Option[T] = 
    for (xp <- x; yp <- y) yield (f(xp,yp)) 

Ce qui est le sucre pour:

x flatMap {xp => y map {yp => f(xp, yp)}} 

Ceci est également possible grâce à l'option étant une monade

+3

C'est bizarre. Je n'ai pas vu la réponse de @ RahulG quand j'ai posté ceci. – user142435

0
def optApply[A,B,C](f: (A, B) => C, a: Option[A], b: Option[B]): Option[C] = 
    a.zip(b).headOption.map { tup => f.tupled(tup) } 

a.zip(b) n'entraîne dans un Iterable [(A, B)] (avec, parce que c'est à partir d'Options, au plus un élément). headOption puis renvoie le premier élément en tant qu'option.

Questions connexes