5

当我有一个Option[T]实例时,很容易T使用诸如map()and之类的单子操作来执行任何操作flatMap()。这样我就不必检查它是否已定义或为空,并将操作链接在一起最终得到一个Option[R]for the result R

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

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

如果不涉及第一个isDefined内部的大量检查,如何优雅地编写这个?map()Option

4

8 回答 8

5

你可以使用这样的东西:

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
于 2015-11-10T18:02:15.377 回答
3

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

val aOpt: Option[Int] = getIntOpt
val bOpt: Option[Int] = getIntOpt

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

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

我只会使用上面的 for-comprehension,然后链接两个调用orElse以提供替代值(如果maxOpt结果是None.

maxOpt orElse aOpt orElse bOpt

orElse的签名:

def orElse[B >: A](alternative: ⇒ Option[B]): Option[B]
于 2015-11-10T17:31:45.850 回答
2

这是另一个fwiw:

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

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

于 2015-11-10T18:38:52.487 回答
1

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

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 操作中使用它。

于 2015-11-10T18:18:12.623 回答
1

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

于 2015-11-10T19:50:21.550 回答
1

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

  def maxOption(opts: Option[Int]*) = {
    val flattened = opts.flatten
    flattened.headOption.map { _ => flattened.max }
  }
于 2015-11-10T20:08:09.140 回答
1

实际上,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
于 2015-11-10T21:33:50.517 回答
1

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 => a与这些操作中的任何一个一起都是semigroup,这是一个可以构建可重用抽象的概念。

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

Haskellsemigroups提供了一个Semigroup类型类和两个包装器类型,Max以及Min,它们通常实现相应的行为。

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

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

...因为它只是<>运算符 forOption (Max a)不值得写。Semigroup您还可以获得在 and 上工作的所有其他实用函数和类,Monoid例如,要找到 a 的最大元素,[Option (Max a)]您只需使用mconcatfunction

scalaz 库带有 aSemigroup和 a特征Monoid以及实现这些特征的Max、和标记MinMaxValMinVal,因此实际上我在 Haskell 中演示的内容也存在于 scalaz 中。

于 2015-11-11T01:47:32.557 回答