SIP public review: explicit Nulls

Good proposal.

There were discussions 7 years ago about this subject; they may contain some considerations that have not yet been brought up here; anyway there is a nice link to the early history of nulls:

2 Likes

As @AndreVanDelft’s links point out, Array would probably require special treatment, because new Array[T](10) should only compile if T >: Null or T is primitive, which is a condition I don’t think can be expressed in user-written code (which thankfully Array isn’t).

Are we planning to add this restriction, or will that just be considered unsound in the same way as uninitialized fields? I’d hope that the weirdness in making Array's constructors irregular wouldn’t be too off-putting.

2 Likes

Thank you @sjrd!

Would be nice to have a UncheckedNulls::asOption method so I can write javaStuff.asOption.getOrElse() instead of ugly if-else stuff. And vice versa maybe, asNullable?

1 Like

You can do Option(javaStuff).getOrElse(...), at least if we change Option.apply to

def apply[T](t: T|Null): Option[t.type & T] =
  if (t == null) None else Some(t)

which flow typing would hopefully accept.

3 Likes

I think you missed the part where the methods of the class are patched as well. So .get on a java Map[A, B]|UncheckedNull is patched to return B|UncheckedNull] instead of B, so you wouldn’t need every type parameter to change.

What if you have a Java method that returns a Scala map?

I believe the proposal has a lot of promise and I would like it to be in the language, and not just under an option.

That said, I think there are several aspects to the current scheme where we need to go back to the drawing board. See my comments in https://github.com/lampepfl/dotty/issues/7828.

2 Likes

For me, the main problem with running into NPEs is when using fields in a class that is initialised at later point, e.g. var foo: String = _. I don’t see how the initialisation problem is addressed by the proposal. If I change all these to var: foo: String | Null = _, I gain nothing, because now I need to check everytime I use the field.

I don’t feel Scala should adopt flow typing. This may work for Java and Kotlin, but feels bad for Scala that doesn’t want to be an imperative language in the first place.

As Lukas said, there is a real danger that we simply obtain now two ways to do the same thing, and null usage will proliferate against classical Option use. Best outcome would be to make Option as performative as possible. I don’t think A | Null needs to be in user code.


Or do the radical step. Make Option[A] = A | Null, and add map, flatMap etc. to it. Stay with pattern matching instead of flow typing, though.

1 Like

That won’t work. A | Null | Null is eqivalent to A | Null, but Option[Option[A]] is not equivalent to Option[A].

If var foo is initialized after construction time, then I don’t see how the type can semantically be just A instead of A | Null or Option[A].

If it’s initialized at construction time then I propose to make a simple main constructor that only saves fields and move the computations to auxiliary constructor or (IMO better) factory method. E.g. replace:

class Xyz {
  var x: X = initializeX
  var y: Y = initializeY(x)
  var z: Z = initializeZ(x, y)
}

with:

class Xyz private (var x: X, var y: Y, var z: Z) {
  def this() = {
    val x = initializeX
    val y = initializeY(x)
    val z = initializeZ(x, y)
    this(x, y, z)
  }
}

or (IMO better):

class Xyz private (var x: X, var y: Y, var z: Z) {
   ...
}

object Xyz {
  def apply(): Xyz = {
    val x = initializeX
    val y = initializeY(x)
    val z = initializeZ(x, y)
    new Xyz(x, y, z)
  }
}

Of course that means changing the problem, but the problem of initialization order seems unsolvable, i.e. there’s no flawless scheme (or is it? any proposals?). Rust (among others) avoids the problem of initialization order by not having constructors. In Rust new instances of structs are made by filling them on allocation. You can’t invoke any method on a struct before it’s fully constructed. Same thing is possible in Java, Scala, etc if you forbid constructors that do anything more than copying data from parameters to fields.

1 Like

There’s another open PR for initialization checking: https://github.com/lampepfl/dotty/pull/7789

2 Likes

Any plan for shorthand? Like T? represents T | Null

1 Like

Remember that null is much less common in Scala than in a language like Java, and AFAIK that’s not intended to change. So I don’t think the shorthand is worth the cost of special syntax…

10 Likes

There’s a design that fixes this: GitHub - sjrd/scala-unboxed-option: A type-parametric unboxed Option type for Scala. Essentially, you box the None instead (and cache it them in object UNone). I’m hoping we can eventually find a way to promote that up to the standard library.

2 Likes

Adding to that, I rarely encounter NullPointerExceptions, and when I do it’s discovered very early in testing and is easily fixed (just Option(nullableExpression)).

Speaking of testing, I wonder how this affects mocking frameworks such as Mockito, which heavily rely on nulls for both implementation and semantics.

6 posts were split to a new topic: Mockito, testing, nulls and exceptions

Let us please stay focused on the proposal at hand in this thread, and stay out of any discussion of whether or not nulls and exceptions are appropriate in tests and/or production code.

1 Like

That’s not entirely what the comments were about. They were also about two other on-topic issues:

  1. Whether a new special syntax is worth the cost (as @jducoeur mentioned).
  2. Whether the SIP is compatible with Mockito, which is a commonly used library.
3 Likes

It makes sense for someone to run tests with explicit Nulls enabled to see the impact? Mocking is (unfortunately) a big part of unit testing.