在 Scala 中处理两个选项

Handling two Options in Scala

我有 2 个选项,我需要对它们所持有的值取平均值。

有可能缺少一个或两个。如果缺少一个值,我会取另一个作为平均值。但如果两者都缺失,我会求助于一些默认值。

如何以干净的方式完成此操作?

我可以使用 isEmpty 检查值是否缺失,但这不是与空值检查相同吗?

我想这是不言自明的:

val option1 = Some(12.0)
val option2 = None
val default = 0.0

val average = (option1, option2) match {
   case (Some(val1), Some(val2)) => (val1 + val2) / 2
   case (None, Some(val2)) => val2
   case (Some(val1), None) => val1
   case (None, None) => default
}

...但如果不是,基本思想是构造一个选项元组,然后对该元组进行模式匹配。

这样做的好处是可以显式捕获所有四种潜在情况 + 得到编译器的支持 - 因为 Option 是一个密封特征,编译器可以检查并确保模式匹配的所有潜在分支都被覆盖.

您可以将选项视为 Seq:

val o: Option[Double]
val p: Option[Double]
val default: Double

val s = o.toSeq ++ p.toSeq

val avg = s.reduceOption(_ + _).getOrElse(default) / 1.max(s.size)
val v = List(opt1, opt2).flatten

if (v.nonEmpty) {
  v.sum / v.size
} else {
  <default value>
}

这可以扩展为使用任意数量的可选值。

另一种可能性:

def avg(left: Option[Double], right: Option[Double])(default: => Double): Double =
  left.flatMap(a => right.map(b => (a + b) / 2))
    .orElse(left)
    .orElse(right)
    .getOrElse(default)

flatMap左边的选项:如果它不为空,你取右边的选项,map它的内容和左边选项的内容平均。如果任一选项为空,则结果为 None,因此您可以使用 orElseleftright 定义为备用值。最后,使用 getOrElse 检索结果,如果两个输入均为空,则返回 default

您可以调整它以采用任何行为。要创建一个在两个选项都为空时抛出的函数,您可以执行以下操作:

val assertAvg = avg(_ : Option[Double], _ : Option[Double])(sys.error("both operands are empty"))

这是有效的,因为 throw 表达式的类型是 Nothing,它是任何其他类型(包括 Double)的子类型,即它可以作为结果返回任何表达式,无论预期类型如何。

代码(和一些测试)可用 here on Scastie

在我看来,你应该在选项中保持平均值,然后选择默认值。

def avgOpt(of:Option[Double]*) = {
  val s = of.flatten
  s.reduceOption(_ + _).map(_ / s.size)
}

avgOpt(Some(5), None, None).getOrElse(0)    //5
avgOpt(Some(5), Some(3), None).getOrElse(0) //4
avgOpt(None, None).getOrElse(0)             //0