Elvis operator for explicit nulls

Scala 3’s explicit null feature is great, but it’s not as easy to work with as it is in Kotlin. There’s an nn method for non-null assertions, but something like Kotlin’s Elvis operator would be pretty great too.

The lack of ?: or something similar is sorely felt when I have multiple values and I want to chain them like I can Options with orElse. Adding something like the Elvis operator feels like a small enough change that it could be added to Scala 3.0. Of course, the operator can’t be named ?:, but something like |? or even elvis with low precedence would be really helpful.

What are your thoughts on this?

For handling expressions that really can be null, the idiomatic approach used most often in the Community Build is Option.apply. The equivalent of the Elvis operator would be Option(_).getOrElse(_).

Is the naive Elvis terrible?

import util.chaining._

/** Alternative to Option(anyref).getOrElse(alt) without widening result. */
extension [A <: AnyRef](anyref: A|Null) inline def ?:(alt: => A): A = if anyref != null then anyref.nn else alt

@main def test() =
  def db(s: String) = println(s"[$s]")
  def x: String = (null: String).tap(db)
  def y = "hello, world".tap(db)
  def z = "scala rocks".tap(db)
  println(x ?: z)
  println(y ?: z)
  println(x ?: y ?: z)

The “value class” encoding does’t allow a by-name class parameter in Scala 2.

I just mentioned on gitter that I accidentally typed:

implicit class Elvis[A](alt: => A) { def ?:(a: A): A = if (a ne null) a else alt }

I didn’t spend more than a few minutes to just try it out, but it’s interesting when you think, “Let me try it out quick,” and then immediately fall into a pit.

Probably someone has already brought up the Elvis Costello operator. But a quick search reveals about The King:

" Elvis was naive, shy and unassertive."

As a partial corrective, in the absence of -Yexplicit-nulls,

else alt.tap(x => assert(x != null))

About the Elvis Costello operator:

He worked as a computer operator for a cosmetics company while trying to make it as a musician in the Seventies.

Yes, I ended up making my own (rather messy) Elvis operator, but it quickly becomes annoying to import it everywhere, and it just seems like a basic feature that a language with explicit nulls should have.

A language with explicit nulls is supposed to discourage you from using nulls, not give you more tools to manipulate them.

12 Likes

I was going to propose the Elvira operator after Cassandra Peterson’s famous coiffure

⸮?

but I guess Elvira is an illegal character.

In any case, if you have to import it everywhere, then probably it is overused.

Hmm, I guess so, but nulls often pop up in my code because of calls to Java libraries.

Sure, but then you should explicitly turn them into Options as soon as possible, rather than keep manipulating them further.

5 Likes

If a Java library is inconvenient or awkward to use from Scala, I find it usually best to concentrate the calls to the library into a limited number of places, essentially building a wrapper or facade.

Of course, many Java libraries are just fine. For example, we all use String, as in java.lang.String.

But there are many reasons to wrap. Besides Java’s use of null, there is also the need to convert between Java and Scala collections and that we often expect things from an API that Java doesn’t offer, such as symbolic operators and methods that take functions as arguments.

3 Likes

I disagree with this sentiment. Kotlin/Typescript style autocasting for nulls and null-coalescing is a joy to work with and going back to Java/Scala Option(al)s feels quite clunky in comparison.

Compare

 foo?.method(bar ?? "default")
 // vs
 Option(foo).map(_.method(Option(bar).getOrElse("default")))
4 Likes

In a world where everything can be null, or where null is used ubiquitously to represent optional values, yes. But Scala doesn’t want to be that world.

6 Likes

I wanted ?. and ?? as well since I deal with alot of interop. But then for some of my needs, it wasn’t just null that I wanted something better, I also had scala.js where js.undefined and null paths needed to be traversed (just like js). But then I also wanted Option, Either and Try to be traversable as well since they can represent “no value.” And then I wanted something that could support indexing.

In js when I had deeply nested paths I had .? everywhere which looked kind of awkward. So I was left scratching my head about a scalable construct so I could use it consistently.

I defined my own ?? to replace getOrElse and it reduced the verbiage quite a bit although it was not perfect. I looked at lenses which may be alot of work to setup but can solve this problem.

So I agree that ?. is great for interop but then I had more needs so I could consistently use it. I don’t think we need something prefect and we don’t want to let perfect be the enemy of good, but I was left with the feeling that scala could do better than js or kotlin in the “traverse data structures/arrays” area. I’m not a purist but I would like something better than what exists elsewhere and has less syntax overhead.

P.S. There is this macro which can help with null traversals: https://index.scala-lang.org/ryanstull/scalanullsafe/scalanullsafe/1.2.5?target=_2.13

2 Likes

Options have its drawbacks, such as extra heap allocation. It is detrimental in performance significant programs. Also giving programmers options is not a bad thing. We are not “stupid” like users and shouldn’t treat us like it.

1 Like

So, I don’t think @sjrd was implying that anybody was stupid here, not even users.

When you design a language, you must be very careful about what is the path of least resistance, because it often dramatically impact the look and feel of the language. As a side note, Scala was repeatedly said to not enforce enough and to give too many choices to do the same things to users.

So, AFAIU, as a general rule Scala discourages the use of null. So it is normal that it is adviced to use Option mappers at the boundaries of your Java/scala systems and to not want to had an elvis operator in the standard library, or in the language itself. Of course, this is OK as long as specific use cases (like the performance oriented one you talked about) are possible - here, it’s the case. If you are performance (mem alloc) conscious, you can make scala code that does not allocate more than java. And lang designer are interested in supporting more use case like that (for ex: see opaque type in scala 3), just not in a way that compromises the fondamental design goals of the language.

(and to make things clear: I’m not part of scalac/lang team, what is above is just my understanding of the state of things)

9 Likes

The standard library should be for things that are used commonly.

There may be good use cases for the Elvis operator, but it is definitely not used very widely.

The threshold for being predefined (i.e. no import needed) is even higher. Even Option requires an import.

Feel free to write your own Elvis. Feel free to share it as part of some library. Maybe others will be happy to use it, too.

4 Likes

Oops, correction: Option does not need an import after all.