Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a concise way to "invert" an Option?

Say I have a function that can take an optional parameter, and I want to return a Some if the argument is None and a None if the argument is Some:

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

So what I want to do is kind of the inverse of map. The variants of orElse are not applicable, because they retain the value of a if it's present.

Is there a more concise way to do this than if (a.isDefined) None else Some(makeB())?

like image 721
Emil Lundberg Avatar asked Mar 23 '18 16:03

Emil Lundberg


3 Answers

Overview of this answer:

  1. One-liner solution using fold
  2. Little demo with the fold
  3. Discussion of why the fold-solution could be just as "obvious" as the if-else-solution.

Solution

You can always use fold to transform Option[A] into whatever you want:

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

Demo

Here is a complete runnable example with all the necessary type definitions:

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))

This outputs:

None
Some(Main$$anon$1$B@5fdef03a)
None
Some(Main$$anon$1$B@48cf768c)

Why fold only seems less intuitive

In the comments, @TheArchetypalPaul has commented that fold seems "lot less obvious" than the if-else solution. I agree, but I still think that it might be interesting to reflect on the reasons why that is.

I think that this is mostly an artifact resulting from the presence of special if-else syntax for booleans.

If there were something like a standard

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

syntax that can be used like this:

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

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

and, more importantly, if this syntax was mentioned on the first page of every introduction to every programming language invented in the past half-century, then the ifNone-otherwise solution (that is, fold), would look much more natural to most people.

Indeed, the Option.fold method is the eliminator of the Option[T] type: whenever we have an Option[T] and want to get an A out of it, the most obvious thing to expect should be a fold(a)(b) with a: A and b: T => A. In contrast to the special treatment of booleans with the if-else-syntax (which is a mere convention), the fold method is very fundamental, the fact that it must be there can be derived from the first principles.

like image 100
Andrey Tyukin Avatar answered Nov 16 '22 03:11

Andrey Tyukin


fold is more concise than pattern matching

 val op:Option[B] = ...

 val inv = op.fold(Option(makeB()))(_ => None)
like image 36
Nazarii Bardiuk Avatar answered Nov 16 '22 05:11

Nazarii Bardiuk


I've come up with this definition 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())
like image 2
nicodp Avatar answered Nov 16 '22 05:11

nicodp