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
?
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.
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.
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.
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.
There’s another open PR for initialization checking: https://github.com/lampepfl/dotty/pull/7789
Any plan for shorthand? Like T?
represents T | Null
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…
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.
Adding to that, I rarely encounter NullPointerException
s, 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 null
s 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 null
s and exceptions are appropriate in tests and/or production code.
That’s not entirely what the comments were about. They were also about two other on-topic issues:
- Whether a new special syntax is worth the cost (as @jducoeur mentioned).
- Whether the SIP is compatible with Mockito, which is a commonly used library.
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.