【问题标题】:Correct way to work with two instances of Option together一起使用两个 Option 实例的正确方法
【发布时间】:2015-11-10 17:24:29
【问题描述】:

当我有一个Option[T] 实例时,使用map()flatMap() 等一元操作对T 执行任何操作都非常容易。这样我就不必检查它是否已定义或为空,并将操作链接在一起最终得到Option[R] 的结果R

我的困难是是否有类似的优雅方式在两个Option[T] 实例上执行功能。

让我们举一个简单的例子,我有两个 val,xy,类型为 Option[Int]。如果它们都被定义,或者如果只定义一个,我想获得它们中的最大值,如果没有定义,我想获得None

如果不涉及第一个 Optionmap() 内的大量 isDefined 检查,如何优雅地编写此代码?

【问题讨论】:

    标签: scala functional-programming scala-option


    【解决方案1】:

    你可以这样使用:

    def optMax(op1:Option[Int], op2: Option[Int]) = op1 ++ op2 match {    
      case Nil => None  
      case list => list.max
    }
    

    或者更好的一个:

    def f(vars: Option[Int]*) = (for( vs <- vars) yield vs).max
    

    @jwvh,感谢您的改进:

    def f(vars: Option[Int]*) = vars.max
    

    【讨论】:

    • 模式匹配顺序需要颠倒,即先匹配Nil。您还应该返回Some(list.max) 而不是list.max,以确保方法的返回类型是Option[Int]
    • 第二个太棒了!
    • @Nyavro,谢谢,我必须查看文档才能找到它)
    • 哇第二个很优雅。
    • 做得很好,但是for 理解非常简单,实际上不需要:vars.map(x=&gt;x).max
    【解决方案2】:

    通常,如果 两个 值都已定义,您会想要做一些事情。 在这种情况下,您可以使用理解:

    val aOpt: Option[Int] = getIntOpt
    val bOpt: Option[Int] = getIntOpt
    
    val maxOpt: Option[Int] = 
        for {
            a <- aOpt
            b <- bOpt
        } yield max(a, b)
    

    现在,您描述的问题并不常见。如果两个值都定义了,您想执行一些操作,但如果只定义了其中一个,您还想检索选项的值。

    如果maxOpt 变成None,我将只使用上面的for-comprehension,然后将两个调用链接到orElse 以提供替代值。

    maxOpt orElse aOpt orElse bOpt
    

    orElse的签名:

    def orElse[B >: A](alternative: ⇒ Option[B]): Option[B]
    

    【讨论】:

    • 感谢您的回复。那么如果ba 不存在,maxOpt 将如何工作?它会产生None吗?就我而言,这种情况很常见(这就是我想找到一个优雅的解决方案的原因),因为我经常必须在两个项目之间进行选择,这两个项目可能都不可用(在这种情况下,我只选择可用的一个)。跨度>
    • @jbx 是的,如果任一输入未定义,maxOpt 将为 None。编译器实际上将 for 理解转换为 aOpt.flatMap { a =&gt; bOpt.map { b =&gt; max(a, b) } }。希望这更有意义。
    • 是的。事实上,我认为我更喜欢它来达到我的目的,这对我来说更清楚。那么这不会使它完整吗? aOpt.flatMap( a =&gt; bOpt.map( b =&gt; Math.max(a, b))).orElse(bOpt)
    • @jbx 不,这还不够。如果aOpt = Some(1)bOpt = None,那么您的表达式可以简化为Some(1).flatMap { _ =&gt; None } orElse None,即None
    【解决方案3】:

    这是另一个fwiw:

    import scala.util.Try
    def maxOpt (a:Option[Int]*)= Try(a.flatten.max).toOption
    

    它适用于 n 个参数(包括零个参数)。

    【讨论】:

      【解决方案4】:

      模式匹配可以让一些东西容易掌握,但这可能不是最优雅的方式:

      def maxOpt[T](optA: Option[T], optB: Option[T])(implicit f: (T, T) => T): Option[T] = (optA, optB) match {
          case (Some(a), Some(b)) => Some(f(a, b))
          case (None, Some(b)) => Some(b)
          case (Some(a), None) => Some(a)
          case (None, None) => None
      }
      

      你最终会得到类似的东西:

      scala> maxOpt(Some(1), None)(Math.max)
      res2: Option[Int] = Some(1)
      

      一旦你有了那个构建块,你就可以在 for-comp 或 monadic 操作中使用它。

      【讨论】:

      • 谢谢。如果函数不是隐式的,我想让它更通用,而不是假设它只是max,这是我真正想要的。
      • 没错,它可以是任何东西。
      【解决方案5】:

      要获得 maxOpt,您还可以使用应用程序,使用 Scalaz 看起来像 (aOpt |@| bOpt) { max(_, _) } & 然后链 orElses 正如@dcastro 建议的那样。

      【讨论】:

        【解决方案6】:

        我假设您期望 Some[Int]|None 作为结果,而不是 Int|None(否则返回类型必须是 Any):

          def maxOption(opts: Option[Int]*) = {
            val flattened = opts.flatten
            flattened.headOption.map { _ => flattened.max }
          }
        

        【讨论】:

          【解决方案7】:

          实际上,Scala 已经或多或少地直接为您提供了这种能力。

          scala> import Ordering.Implicits._
          import Ordering.Implicits._
          
          scala> val (a,b,n:Option[Int]) = (Option(4), Option(9), None)
          a: Option[Int] = Some(4)
          b: Option[Int] = Some(9)
          n: Option[Int] = None
          
          scala> a max b
          res60: Option[Int] = Some(9)
          
          scala> a max n
          res61: Option[Int] = Some(4)
          
          scala> n max b
          res62: Option[Int] = Some(9)
          
          scala> n max n
          res63: Option[Int] = None
          

          【讨论】:

          • Max 只是一个例子。这个问题并不是专门关于最大的。它是关于将任何可能的函数(T, T) =&gt; T 应用转换为(Option[T], Option[T]) =&gt; Option[T],如果任何一个参数是None,则返回另一个,如果两者都是None,则返回None。如果两者都不是None,则应用某个函数。
          【解决方案8】:

          Haskell-ish 对这个问题的看法是观察以下操作:

          max, min :: Ord a => a -> a -> a
          max a b = if a < b then b else a
          min a b = if a < b then a else b
          

          ...是关联的

          max a (max b c) == max (max a b) c
          min a (min b c) == min (min a b) c
          

          因此,任何类型的Ord a =&gt; a 连同这些操作中的任何一个都是半群,这是一个可以构建可重用抽象的概念。

          您正在处理Maybe(Haskell 表示“选项”),它向基本a 类型添加了一个通用的“中性”元素(您希望max Nothing x == x 保持为法律)。这将带您进入 monoids,它是半群的一个子类型。

          Haskell semigroups library 提供了一个Semigroup 类型类和两个包装类型MaxMin,它们通常实现相应的行为。

          由于我们正在处理Maybe,就该库而言,捕获您想要的语义的类型是Option (Max a)——一个与Max半群具有相同二元运算的幺半群,并使用@987654341 @ 作为标识元素。那么函数就变成了:

          maxOpt :: Ord a => Option (Max a) -> Option (Max a) -> Option (Max a)
          maxOpt a b = a <> b
          

          ...因为它只是 Option (Max a)&lt;&gt; 运算符,所以不值得写。您还可以获得在SemigroupMonoid 上工作的所有其他实用程序函数和类,例如,要找到[Option (Max a)] 的最大元素,您只需使用the mconcat function

          scalaz 库带有 SemigroupMonoid 特征,以及实现这些特征的 Max, Min, MaxVal and MinVal tags,所以实际上我在 Haskell 中演示的内容也存在于 scalaz 中。

          【讨论】:

            猜你喜欢
            • 2011-04-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-04-09
            • 2011-06-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多