Proposal: getOrElse syntactic sugar

I’d like to suggest syntax sugar like below.

    val maybeA = None
    val maybeB = None
    val maybeC = Some(3)

    val x: Int = getOrElse {
      a <- maybeA
      b <- maybeB
      c <- maybeC
    } yield 0
    // x == 3

translated into

    val maybeA = None
    val maybeB = None
    val maybeC = Some(3)

    val x = maybeA orElse maybeB orElse maybeC getOrElse 0
    // x == 3

It appears you cannot use the variables a, b and c, because evaluation only continues when they are undefined. Why even name them?

Why not define some method like:


getFirst(maybeA, maybeB, maybeC)(0)

5 Likes

Given that, this seems to work.

def getFirst[A](as: Option[A]*)(default: A): A = {
    as.reduceLeft((o1, o2) => if (o1.nonEmpty) o1 else o2).getOrElse(default)
}

I dislike the if, but it was easy to write and nothing else occurred to me quickly. This seems simple enough that it doesn’t need syntax to clutter up the language.

3 Likes

Why even name them?

I didn’t think of it until you said. They are unnecessary.

Or simply

def getFirst[A](as: Option[A]*)(default: A): A = {
  as.reduceLeft(_ orElse _).getOrElse(default)
}
4 Likes

Please let me explain further about this topic.

At first, I suggested that sugar because I want to avoid below:

  • writing long expression as orElse argument
  • writing long orElse method chain

But it seems unnecessary by using getFirst.

That’s the function I wanted!

Or:

def getFirst[A](as: Option[A]*)(default: A): A =
  as.find(_.isDefined).getOrElse(default)

That even works for empty as.

2 Likes

That needs extra .flatten. Below code works:

def getFirst[A](as: Option[A]*)(default: A): A =
  as.find(_.isDefined).flatten.getOrElse(default)

I have even shorter alternative:

def getFirst[A](as: Option[A]*)(default: A): A =
  as.foldRight(default)(_ getOrElse _)

:slight_smile:

2 Likes

How about adding || to Option as a synonym for orElse? So you could write it as:


maybeA || maybeB || maybeC || Some(3)

And how about a && method such that


Some(a) && Some(b) == Some((a, b))

Some(a) && Some(b) && Some(c) == Some((a,b,c))

and None if any operand is None?

This is zip

This isn’t compatible with the previous snippet, because of associativity. It would have to be Some(((a, b), c)) (which is what zip would give you).

2 Likes

You’d need implicit machinery for the && case to not just get nested tuples. Also in general I’m iffy on adding more symbolic operators here.

Let me add my own stone to the code-golfing edifice: the default value should probably be by-name, to be consistent with the usual semantics:

def getFirst[A](as: Option[A]*)(default: => A): A =
  as.collectFirst{ case Some(v) => v }.getOrElse(default)
6 Likes

I really don’t think the suggested “getOrElse-comprehension” is less verbose than just chaining orElse, or suggestions above.

3 Likes