一起使用两个 Option 实例的正确方法
Correct way to work with two instances of Option together
当我有一个 Option[T]
实例时,可以很容易地使用 map()
和 flatMap()
等一元操作对 T
执行任何操作。这样我就不必检查它是否已定义或为空,也不必将操作链接在一起最终得到结果 R
的 Option[R]
。
我的困难是是否有类似优雅的方式在两个 Option[T]
实例上执行功能。
让我们举一个简单的例子,我有两个类型为 Option[Int]
的值,x
和 y
。如果它们都被定义了,我想得到它们的最大值,或者如果只定义了一个,我想得到它们的最大值,如果定义了 none,则 None
。
如何在第一个 Option
的 map()
中不涉及大量 isDefined
检查的情况下优雅地编写此代码?
通常,如果两个 值都已定义,您会想要做一些事情。
在这种情况下,您可以使用 for-comprehension:
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,然后在 maxOpt
结果为 None
时,将两个调用链接到 orElse
以提供替代值。
maxOpt orElse aOpt orElse bOpt
orElse
的签名:
def orElse[B >: A](alternative: ⇒ Option[B]): Option[B]
你可以这样使用:
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
模式匹配会让一些东西容易掌握,但这可能不是最优雅的方式:
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 操作中使用它。
这是另一个 fwiw:
import scala.util.Try
def maxOpt (a:Option[Int]*)= Try(a.flatten.max).toOption
它适用于 n 个参数(包括零个参数)。
要获得 maxOpt,您还可以使用应用程序,使用 Scalaz 看起来像 (aOpt |@| bOpt) { max(_, _) } & 然后按照 @dcastro 的建议链接 orElses。
我假设您期望结果是 Some[Int]|None
,而不是 Int|None
(否则 return 类型必须是 Any
):
def maxOption(opts: Option[Int]*) = {
val flattened = opts.flatten
flattened.headOption.map { _ => flattened.max }
}
实际上,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
一个 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 代表 "option"),它向基础 a
类型添加了一个通用 "neutral" 元素(您希望 max Nothing x == x
成为一项法律)。这将带您进入 monoids,它是半群的一个子类型。
Haskell semigroups
library provides a Semigroup
type class and two wrapper types, Max
and Min
,一般实现相应的行为。
由于我们正在处理 Maybe
,就该库而言,捕获您想要的语义的类型是 Option (Max a)
——一个与 [=19 具有相同二进制操作的幺半群=] semigroup,并使用 Nothing
作为标识元素。那么函数就变成了:
maxOpt :: Ord a => Option (Max a) -> Option (Max a) -> Option (Max a)
maxOpt a b = a <> b
...因为它只是 Option (Max a)
的 <>
运算符,所以不值得写。您还获得了所有其他效用函数和 类 适用于 Semigroup
和 Monoid
,因此例如要查找 [Option (Max a)]
的最大元素,您只需使用 the mconcat
function.
scalaz 库带有实现这些特性的 Semigroup
and a Monoid
trait, as well as Max
, Min
, MaxVal
and MinVal
tags,所以实际上我在 Haskell 中演示的内容也存在于 scalaz 中。
当我有一个 Option[T]
实例时,可以很容易地使用 map()
和 flatMap()
等一元操作对 T
执行任何操作。这样我就不必检查它是否已定义或为空,也不必将操作链接在一起最终得到结果 R
的 Option[R]
。
我的困难是是否有类似优雅的方式在两个 Option[T]
实例上执行功能。
让我们举一个简单的例子,我有两个类型为 Option[Int]
的值,x
和 y
。如果它们都被定义了,我想得到它们的最大值,或者如果只定义了一个,我想得到它们的最大值,如果定义了 none,则 None
。
如何在第一个 Option
的 map()
中不涉及大量 isDefined
检查的情况下优雅地编写此代码?
通常,如果两个 值都已定义,您会想要做一些事情。 在这种情况下,您可以使用 for-comprehension:
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,然后在 maxOpt
结果为 None
时,将两个调用链接到 orElse
以提供替代值。
maxOpt orElse aOpt orElse bOpt
orElse
的签名:
def orElse[B >: A](alternative: ⇒ Option[B]): Option[B]
你可以这样使用:
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
模式匹配会让一些东西容易掌握,但这可能不是最优雅的方式:
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 操作中使用它。
这是另一个 fwiw:
import scala.util.Try
def maxOpt (a:Option[Int]*)= Try(a.flatten.max).toOption
它适用于 n 个参数(包括零个参数)。
要获得 maxOpt,您还可以使用应用程序,使用 Scalaz 看起来像 (aOpt |@| bOpt) { max(_, _) } & 然后按照 @dcastro 的建议链接 orElses。
我假设您期望结果是 Some[Int]|None
,而不是 Int|None
(否则 return 类型必须是 Any
):
def maxOption(opts: Option[Int]*) = {
val flattened = opts.flatten
flattened.headOption.map { _ => flattened.max }
}
实际上,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
一个 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 代表 "option"),它向基础 a
类型添加了一个通用 "neutral" 元素(您希望 max Nothing x == x
成为一项法律)。这将带您进入 monoids,它是半群的一个子类型。
Haskell semigroups
library provides a Semigroup
type class and two wrapper types, Max
and Min
,一般实现相应的行为。
由于我们正在处理 Maybe
,就该库而言,捕获您想要的语义的类型是 Option (Max a)
——一个与 [=19 具有相同二进制操作的幺半群=] semigroup,并使用 Nothing
作为标识元素。那么函数就变成了:
maxOpt :: Ord a => Option (Max a) -> Option (Max a) -> Option (Max a)
maxOpt a b = a <> b
...因为它只是 Option (Max a)
的 <>
运算符,所以不值得写。您还获得了所有其他效用函数和 类 适用于 Semigroup
和 Monoid
,因此例如要查找 [Option (Max a)]
的最大元素,您只需使用 the mconcat
function.
scalaz 库带有实现这些特性的 Semigroup
and a Monoid
trait, as well as Max
, Min
, MaxVal
and MinVal
tags,所以实际上我在 Haskell 中演示的内容也存在于 scalaz 中。