Правильный способ совместной работы с двумя экземплярами 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.

Другие вопросы по тегам