Правильный способ совместной работы с двумя экземплярами Option
Когда у меня есть один Option[T]
Например, довольно легко выполнить любую операцию T
используя монадические операции, такие как map()
а также flatMap()
, Таким образом, мне не нужно делать проверки, чтобы определить, определено ли оно или пусто, и объединить операции, чтобы в конечном итоге получить Option[R]
за результат R
,
Моя проблема заключается в том, есть ли подобный элегантный способ выполнять функции на двух Option[T]
экземпляров.
Давайте возьмем простой пример, где у меня есть два значения, x
а также y
типа Option[Int]
, И я хочу получить максимум из них, если они оба определены, или тот, который определен, если определен только один, и None
если они не определены
Как бы написать это элегантно, не вовлекая много isDefined
проверяет внутри map()
из первых Option
?
8 ответов
Вы можете использовать что-то вроде этого:
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
Обычно вы хотите что-то сделать, если оба значения определены. В этом случае вы можете использовать для понимания:
val aOpt: Option[Int] = getIntOpt
val bOpt: Option[Int] = getIntOpt
val maxOpt: Option[Int] =
for {
a <- aOpt
b <- bOpt
} yield max(a, b)
Теперь проблема, которую вы описали, не так распространена. Вы хотите что-то сделать, если оба значения определены, но вы также хотите получить значение параметра, если определено только одно из них.
Я бы просто использовал вышеизложенное для понимания, а затем связал два вызова orElse
предоставить альтернативные значения, если maxOpt
оказывается None
,
maxOpt orElse aOpt orElse bOpt
orElse
подпись:
def orElse[B >: A](alternative: ⇒ Option[B]): Option[B]
Вот еще один рассказ:
import scala.util.Try
def maxOpt (a:Option[Int]*)= Try(a.flatten.max).toOption
Он работает с n аргументами (включая ноль аргументов).
Чтобы получить maxOpt, вы также можете использовать аппликатив, который при использовании Scalaz будет выглядеть (aOpt |@| bOpt) { max(_, _) }, а затем цепочка orElses, как предложено @dcastro.
Сопоставление с образцом позволит легко что-то понять, но это может быть не самым элегантным способом:
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)
Как только у вас есть это здание, блок, вы можете использовать его для внутренних или монадических операций.
На самом деле, 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
Я полагаю, вы ожидаете Some[Int]|None
в результате не Int|None
(в противном случае тип возвращаемого значения должен быть Any
):
def maxOption(opts: Option[Int]*) = {
val flattened = opts.flatten
flattened.headOption.map { _ => flattened.max }
}
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
вместе с любой из этих операций создается полугруппа, концепция, для которой можно построить повторно используемые абстракции.
И ты имеешь дело с Maybe
(Haskell для "option"), который добавляет базовый "нейтральный" элемент к базе a
тип (вы хотите max Nothing x == x
провести как закон). Это приводит вас к моноидам, которые являются подтипом полугрупп.
Хаскелл semigroups
библиотека обеспечивает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
... который, так как это просто<>
оператор дляOption (Max a)
Не стоит писать. Вы также получаете все другие вспомогательные функции и классы, которые работают на Semigroup
а также Monoid
так, например, чтобы найти максимальный элемент [Option (Max a)]
вы бы просто использовать mconcat
функция
Библиотека скалаза поставляется сSemigroup
иMonoid
черта, а также Max
, Min
, MaxVal
а такжеMinVal
теги, которые реализуют эти черты, так что на самом деле то, что я продемонстрировал здесь, в Haskell, существует и в scalaz.