Today an issue was raised on Discord that caught my attention. As you might know, Scala supports a kind of flow typing for nullable types, which is why this works:
def emptyOr(x: String | Null): String =
if x ne null then
x // x is String here, not String | Null
else
""
But as soon as any type is involved that isn’t a subtype of AnyRef
, it no longer compiles:
def zeroOr(x: Int | Null): Int =
if x ne null then
x
else
0
//-- [E008] Not Found Error: -----------------------------------------------------
// | if x ne null then
// | ^^^^
// | value ne is not a member of Int | Null.
An alternative would be to use !=
instead of ne
, but then it works for neither of the two examples above because a CanEqual[String | Null, Null]
or CanEqual[Int | Null, Null]
is not available.
Values of types String | Null and Null cannot be compared with == or !=
How do we fix this? Do we just need a given CanEqual[Any, Null]
in the standard library?
scala.Predef
already defines an ne
extension method for AnyRef | Null
, I guess it would not be a stretch to define one for Any | Null
I have tried this in Scala 3.7.2, which includes that patch.
Any | Null
is equivalent to Any
, so we’d have to live with the fact that we can now call eq
on anything including non-reference types. But I can’t think of a better solution either, my CanEqual
suggestion from above has the same problem.
@sjrd said on Discord that null tests shold be done with ==
/!=
rather than eq
/ne
, and for that we would need the CanEqual
instance. But we could also just provide both CanEqual
and eq
/ne
. I think of ==
as value equality and eq
as reference equality, and since we’re testing for equality with the null reference, I personally prefer eq
, but I’m not too dogmatic about it.
There is one case that null tests are done via ==
in scala.
val data: Null | Int = ???
data match
case null => 13
case _ => data
In pattern matching, the case null
is checked via ==
. And if we check using case _: Null
then it won’t work since Null
is not a runtime type. Maybe when explicit-nulls enabled, we can make csae _: Null
a special case for type checking just via eq null
or == null
(I don’t know which is better)