【问题标题】:How to combine Option values in Scala?如何在 Scala 中组合选项值?
【发布时间】:2010-04-26 09:42:35
【问题描述】:

我希望能够在 Scala 中将操作 f: (T,T) => T 应用于 Option[T] 值。如果两个值中的任何一个为None,我希望结果为None

更具体地说,我想知道是否有更短的方法来执行以下操作:

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

我尝试了(x zip y) map {case (u,v) => f(u,v)},但结果是Iterator[T] 而不是Option[T]

【问题讨论】:

    标签: scala type-conversion scalaz


    【解决方案1】:
    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
    

    【讨论】:

      【解决方案2】:

      @RahulG 的回答利用了 Option 是一个单子这一事实(即使 Scala 库中没有表示它的类型)。编译器将for 理解扩展为以下内容:

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

      在 Scalaz 的帮助下,您也可以将其视为应用函子:

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

      一个关键的区别是,在单子计算中,计算a 的失败(即None)会使评估短路。在 applicative 样式中,ab 都被评估,如果两者都是 Somes,则调用纯函数。您还可以看到,在 monadic 计算中,值 aa 可能已用于计算 b;在应用版本中,b 不能依赖于a 的结果。

      【讨论】:

      • 这个方法的 ASCII 码是 &lt;|*|&gt; 吗?
      • &lt;*&gt; 允许您提供“纯”功能,在本例中为(a, b)=&gt; a + b&lt;|*|&gt; 便于将Tuple2.apply 用作纯函数。 实际上比 arity-2 更通用一点,你可以写成 (a ⊛ b a ⊛ b) {_ + _ + _ + _}。这有点实验性,因此还没有 ASCII 别名。
      • 错字,我的意思是:(a ⊛ b ⊛ a ⊛ b) {_ + _ + _ + _}
      • 从第 7 版的 Scalaz ASCII 别名到 操作符是|@|:val calc: Option[Int] = (a |@| b) {_ + _}
      【解决方案3】:

      我的 scalaz 版本比 retronym 稍旧,但以下对我来说是一个示例,并且可以推广到您有 3 种类型的情况T, U, V而不仅仅是一个:

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

      然后我可以添加:

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

      【讨论】:

      • &lt;|*|&gt;替换为&lt;*&gt;以避免创建临时元组,直接使用f
      【解决方案4】:

      Scala 2.13 开始,Option#zip 可以应用于另一个Option 以返回Option(在早期版本中,它会返回Iterable);因此:

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

      zip 的行为在哪里:

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

      并且可以这样应用:

      optApply[Int]((a, b) => a max b, Some(2), Some(5)) // Some(5)
      optApply[Int]((a, b) => a max b, Some(2), None)    // None
      

      【讨论】:

        【解决方案5】:

        您可以用于推导式:

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

        什么是糖:

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

        这也是可能的,因为 Option 是 Monad

        【讨论】:

        • 这很奇怪。发布此消息时,我没有看到 @RahulG 的答案。
        【解决方案6】:
        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) 确实会产生一个 Iterable[(A, B)] (因为它来自选项,所以最多一个元素)。 headOption 然后将第一个元素作为选项返回。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-06-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多