Идиоматическая скала для получения одной опции из двух опций и выдачи исключения, если две доступны

val one: Option[Int] = None    
val two = Some(2)

Option(one.getOrElse(two.getOrElse(null))) // Gives me Some(2) which I want

val one = Some(1)
val two = None

Option(one.getOrElse(two.getOrElse(null))) // Gives me Some(1) which I want

val one: Option[Int] = None
val two: Option[Int] = None

Option(one.getOrElse(two.getOrElse(null))) // Gives me None which I want

val one = Some(1)
val two = Some(2)

Option(one.getOrElse(two.getOrElse(null))) // Gives me Some(1) when I want an exception

Я кратко рассмотрел тип Either, но кажется, что он предназначен для "Представления значения одного из двух возможных типов". Я пропускаю какую-то структуру данных или монаду? По сути, я хочу, чтобы явное (и выдача ошибок, если оба являются ценными) получить либо один, если он доступен, либо получить None

8 ответов

Решение

Я не знаю ни одной предварительной сборки, чтобы сделать это, поэтому вот функция:

def xor[T](x: Option[T], y: Option[T]): Option[T] = (x, y) match {
    case (Some(_), None) => x
    case (None, Some(_)) => y
    case (None, None) => None
    case _ => throw new Exception()
}
def xor[T](a: Option[T], b: Option[T]) = 
    a orElse b ensuring (_ => a zip b isEmpty)

Вот моя версия функции

val options = Seq(one, two).flatten
if (options.size > 1) throw new Exception("two options were provided")
options.headOption

Я бы, наверное, пошел в старую школу, если бы еще, для простого случая.

scala> implicit class optxor[A](val opt: Option[A]) extends AnyVal {
     | def xor(other: Option[A]) = if (opt.isEmpty) other else if (other.isEmpty) opt else ??? }
defined class optxor

scala> one xor two
res18: Option[Int] = Some(2)

scala> two xor three
scala.NotImplementedError: an implementation is missing
  at scala.Predef$.$qmark$qmark$qmark(Predef.scala:225)
  at optxor$.xor$extension(<console>:8)
  ... 33 elided

Мы можем объединить 2 Option с как Iterable с Option "s ++ оператор, и, следовательно, сопоставление с образцом на итерациях вместо набора опций:

optionA ++ optionB match {
  case Seq(x, y) => throw new Exception
  case x => x.headOption
}
// None and None       => None
// Some(5) and None    => Some(5)
// None and Some(5)    => Some(5)
// Some(5) and Some(3) => Exception

Заметка headOption который прекрасно обрабатывает как список с 1 элементом, так и пустой список.

Я бы подошел к этому, используя функцию сопоставления с образцом, как ответил gzou, но вот один из них:

one.map{x => two.foreach(y => throw new Exception("both defined")); x}.orElse(two)

Как насчет этого?

import scala.util.Try

object XorOption {

  def xorOptions[T](one: Option[T], two: Option[T]): Try[Option[T]] = {
    Try {
      for (x <- one; y <- two) throw new RuntimeException
      one.orElse(two)
    }
  }

  def main(args: Array[String]) {
    val testData = List(
      (None, None),
      (None, Some(2)),
      (Some(1), None),
      (Some(1), Some(2)))

    for (t <- testData) {
      println(t + " => " + xorOptions(t._1, t._2))
    }

  }

}

Выход:

(None,None) => Success(None)
(None,Some(2)) => Success(Some(2))
(Some(1),None) => Success(Some(1))
(Some(1),Some(2)) => Failure(java.lang.RuntimeException)
List(Some(1), Some(2)).flatten match {
   case x :: Nil => Some(x)
   case Nil => None
   case _ => throw new Exception
}

Или же,

scala> val one: Option[Int] = None
one: Option[Int] = None

scala> val two = Option(2)
two: Option[Int] = Some(2)

scala> val three = Option(3)
three: Option[Int] = Some(3)

scala> (for (_ <- one; _ <- two) yield ???) orElse one orElse two
res0: Option[Int] = Some(2)

scala> (for (_ <- three; _ <- two) yield ???) orElse three orElse two
scala.NotImplementedError: an implementation is missing
  at scala.Predef$.$qmark$qmark$qmark(Predef.scala:225)
  at $anonfun$1$$anonfun$apply$1.apply(<console>:10)
  at $anonfun$1$$anonfun$apply$1.apply(<console>:10)
  at scala.Option.map(Option.scala:145)
  at $anonfun$1.apply(<console>:10)
  at $anonfun$1.apply(<console>:10)
  at scala.Option.flatMap(Option.scala:170)
  ... 33 elided

безнадежно

scala> one flatMap (_ => two) map (_ => ???) orElse one orElse two
res3: Option[Int] = Some(2)

но по идиоматическим причинам трудно представить, что можно сделать что-то из этого.

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