How to combine Option values in Scala?

廉价感情. 提交于 2019-11-28 22:42:44
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
retronym

@RahulG's answer exploits the fact that Option is a monad (even though there is no type to represent this in the Scala library). The compiler expands the for comprehension to the following:

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

You can also treat it as an applicative functor, with some help from Scalaz:

import scalaz._
import Scalaz._

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

A key difference is that in the monadic calculation, a failure (that is, None) of calculation a short circuits the evaluation. In the applicative style, both a and b are evaluated, and if both are Somes, the pure function is called. You can also see that in the monadic calculation, the value aa could have been used in the calculation b; in the applicative version, b cannot depend on the result of a.

I have a slightly older version of scalaz than retronym but the following works for me as an example and is generalizable for the case where you have 3 types T, U, V and not just one:

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])
}

I can then add:

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

You can use for comprehensions:

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))

Which is sugar for:

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

This is also possible due to Option being a Monad

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) does result in an Iterable[(A, B)] (with, because it's from Options, at most one element). headOption then returns the first element as an Option.

Starting Scala 2.13, Option#zip can be applied to another Option to return an Option (in earlier versions, it would have returned an Iterable); thus:

def optApply[T](f: (T,T) => T, a: Option[T], b: Option[T]): Option[T] =
  a.zip(b).map(f.tupled)

where the behavior of zip is:

Some(2).zip(Some(3)) // Some((2, 3))
Some(2).zip(None)    // None
None.zip(Some(3))    // None
None.zip(None)       // None

and which can be applied as such:

optApply[Int]((a, b) => a max b, Some(2), Some(5)) // Some(5)
optApply[Int]((a, b) => a max b, Some(2), None)    // None
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!