Есть ли более простой способ "инвертировать" вариант?

Скажем, у меня есть функция, которая может принимать необязательный параметр, и я хочу вернуть Some если аргумент None и a None если аргумент Some:

def foo(a: Option[A]): Option[B] = a match {
  case Some(_) => None
  case None    => Some(makeB())
}

Поэтому я хочу сделать вид инверсии map. Варианты orElse неприменимы, поскольку они сохраняют значение a если оно присутствует.

Есть ли более краткий способ сделать это, чем if (a.isDefined) None else Some(makeB())?

Ответ 1

Обзор этого ответа:

  1. Однослойное решение с использованием fold
  2. Маленькая демонстрация со fold
  3. Обсуждение того, почему fold -solution может быть таким же "очевидным", как if-else -solution.

Решение

Вы всегда можете использовать fold для преобразования Option[A] во все, что вы хотите:

a.fold(Option(makeB())){_ => Option.empty[B]}

демонстрация

Ниже приведен полный пример со всеми необходимыми определениями типов:

class A
class B
def makeB(): B = new B

def foo(a: Option[A]): Option[B] = a match {
  case Some(_) => None
  case None    => Some(makeB())
}

def foo2(a: Option[A]): Option[B] = 
  a.fold(Option(makeB())){_ => Option.empty[B]}

println(foo(Some(new A)))
println(foo(None))
println(foo2(Some(new A)))
println(foo2(None))

Эти результаты:

None
Some([email protected])
None
Some([email protected])

Почему fold кажется менее интуитивным

В комментариях @TheArchetypalPaul прокомментировал, что fold кажутся "намного менее очевидными", чем решение if-else.

Я бы сказал, что это в основном артефакт, вызванный наличием специального if-else синтаксиса для булевых.

Если бы что-то вроде стандартного

def ifNone[A, B](opt: Option[A])(e: => B) = new {
  def otherwise[C >: B](f: A => C): C = opt.fold((e: C))(f)
}

синтаксис, который можно использовать следующим образом:

val optStr: Option[String] = Some("hello")

val reversed = ifNone(optStr) { 
  Some("makeB") 
} otherwise {
  str => None
}

и, что более важно, если бы этот синтаксис был упомянут на первой странице каждого введения на каждый язык программирования, изобретенный за последние полвека, тогда решение ifNone-otherwise (то есть fold) выглядело бы намного более естественным для большинства людей,

Действительно, метод Option.fold является устранителем типа Option[T]: всякий раз, когда у нас есть Option[T] и вы хотите получить A из нее, наиболее очевидной ожидаемой задачей должно быть fold(a)(b) с a: A и b: T => A В отличие от специальной обработки булевых элементов с if-else -syntax (что является простым соглашением) метод fold является очень фундаментальным, тот факт, что он должен быть там, может быть получен из первых принципов.

Ответ 2

fold более кратким, чем сопоставление с образцом

 val op:Option[B] = ...

 val inv = op.fold(Option(makeB()))(_ => None)

Ответ 3

Я придумал это определение: a.map(_ => None).getOrElse(Some(makeB())):

scala> def f[A](a: Option[A]) = a.map(_ => None).getOrElse(Some(makeB()))
f: [A](a: Option[A])Option[makeB]

scala> f(Some(44))
res104: Option[makeB] = None

scala> f(None)
res105: Option[makeB] = Some(makeB())