Improvements optional chaining (direct style) (explicit nulls) (user space)

Recently got interest on scala because of 99% imminent arrival of first class structural types aka “named tuples” Pre-SIP, had chosen Kotlin before because has less constructs and faster learning curve with null safety, having said that, only scala has some of my favorite language features: union/literal types and type level programming

People here like monads, also understand only type category gurus formally understand them, for the rest they are some form of next-gen burritos, having said that, have no interest about knowing what a monoid is, less to know what endofunctors have to do with that

Derived from the above Option[T] is the favorite tool to handle “optional” values, some may defend it to death

Still cannot ignore the fact that virtually any other “recent or evolving” high level programming language that inherited the “implicit null billion dollar mistake” has some kind of workaround at the type level aka “explicit nulls/optionals” and direct style operations on them aka optional chaining

This is not a post to include optional chaining in standard library, nor a Pre SIP, (so you can stop being defensive about why your Option[T] is better), but to open a technical disccussion on how to improve my optional chaining syntax for explicit nulls on user space.

Usage

val typeSafeString: String | Null = null

typeSafeString
  .?("Hello " + _)
  .?(_.reverse)
  .?(_.toUpperCase)
  .?=("default")

The syntax may appear very decent, would be nicer to call members directly with ?. instead of inside the lambda

Implementation so far

  • For sake of simplicity used ?= instead of ?: as it works slightly different (Right associative)
  • Extension methods
  • Inline
/** Explicit nulls extensions `T | null` */
extension [T](t: T | Null)
  /** Call the provided function if object is not null
   * otherwise return same null
   */
  inline def ?[U](f: T => U): U | Null =
    if t == null 
    then null else f(t)
  /** Provides a default value if the object is null */
  inline infix def ?=[U >: T](u: U): U =
    if t == null 
    then u else t

Looking for

  • Replace lamba syntax with member access: optional.?(_.method) => optional?.method
  • If above is not possible, then also inline the lambda param of .?
  • Early break of optional chain if value is null, in other words skip subsequent .? calls

Just inline def ?[U](inline f: T => U) and you’ll be about as close as you conveniently can be with Scala 3. The JIT compiler will probably catch the successive null checks and just jump to the end, so unless microbenchmarking suggests a substantial penalty, I wouldn’t worry about tree rewriting.

Although it might technically be possible to use Dynamic (or macros) to get .?.method syntax to work, I advise against it. It would be a lot of machinery, and probably would fail in some weird corner cases, and would confuse the heck out of people reading your code.

Honestly, I also advise against | Null as a language construct you want to have floating around. But if you want to do it, inlining the function is a big win for extremely little effort, because it then writes code pretty much as you would by hand.

(Aside: I think Rust’s ? is waaaaaaay better than Kotlin’s ?. Tidy direct error handling is a much bigger deal than being allowed to chain nulls all over the place.)

2 Likes

Here is a version of ? That behaves like in Rust on Options, but allows long-jumps as well. scala-days-2023/src/main/scala/jsonlib/util/optional.scala at main · nicolasstucki/scala-days-2023 · GitHub

5 Likes

Oh, I’ve had my own for years! Latest iteration (way easier in Scala 3 than it was in Scala 2): kse3/flow/src/Flow.scala at main · Ichoran/kse3 · GitHub

2 Likes