Правильный способ работы с двумя экземплярами Option вместе

Обновить

December 2018

Просмотры

203 раз

5

Когда у меня есть один Option[T]экземпляр довольно легко выполнять любые операции с Tиспользованием монадических таких операций, как map()и flatMap(). Таким образом , я не должен делать проверку , чтобы увидеть , будет ли она определена или пуста, и цепные операции вместе , чтобы в конечном итоге получаете Option[R]результата R.

Моя трудность заключается в том , есть ли подобный элегантный способ для выполнения функций на два Option[T]случаях.

Давайте рассмотрим простой пример , в котором у меня есть два Vals, xи yтипа Option[Int]. И я хочу , чтобы получить максимум из них , если они оба определены, или тот , который определен , если только одна определена, и , Noneесли ничего не определено.

Как бы один написать это элегантно , не привлекая множество isDefinedпроверок внутри map()первого Option?

jbx

8 ответы

1

Haskell-иш взять на этот вопрос заключается в наблюдении, что следующие операции:

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 для «опции»), который добавляет общий «нейтральный» элемент базового aтипа (вы хотите max Nothing x == xпровести в качестве закона). Это приведет вас в моноиды , которые являются подтипом полугрупп.

Haskell semigroupsбиблиотека предоставляет Semigroupкласс типа и два типа оболочки, Maxи Min, что в общем реализовать соответствующие модели поведения.

Так как мы имеем дело с Maybe, с точкой зрения этой библиотеки типов , которая захватывает семантику вы хотите это Option (Max a)-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функции .

Библиотека scalaz поставляется с Semigroupи на Monoidчерт, а также Max, Min, MaxValи MinValтегов , которые реализуют эти черты, так и в самом деле материал , который я продемонстрировал здесь , в Haskell существует в scalaz , а также.

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)

После того как вы это здание, блок, вы можете использовать его в обмен на комп или монадические операции.

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
2

Вот еще FWIW:

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

Он работает с п аргументов (в том числе без аргументов).

1

Чтобы получить maxOpt, вы можете также использовать аппликативном, который с помощью Scalaz будет выглядеть (Aopt | @ | Bopt) {тах (_, _)} и затем цепь orElses, как @dcastro предложил.

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
3

Как правило, вы хотите сделать что - то , если оба определены значения. В этом случае, вы могли бы использовать для-понимания:

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«S подпись:

def orElse[B >: A](alternative: ⇒ Option[B]): Option[B]
1

Я предполагаю , что вы ожидаете Some[Int]|Noneв результате, а не Int|None( в противном случае тип возвращаемого должен быть Any):

  def maxOption(opts: Option[Int]*) = {
    val flattened = opts.flatten
    flattened.headOption.map { _ => flattened.max }
  }