If we were paying the full cost of nullable types (T?), then there would be an obvious choice: T would be a non-nullable type pattern, and T? would be a nullable type pattern.  But introducing an `any Foo` notion has similar conceptual surface area, but dramatically less utility.  So the "return on syntax" for "any Foo" is not good.

We were considering T? at one point, back when we were considering T? over in Valhalla land, but as soon as that didn't pan out over there, the attractiveness of using it over here pretty quickly went to zero.


The argument for using totality to wriggle out of the nullity trap is a sophisticated one, which may be part of the problem, but it is largely about uniformity (and partially a pick-your-poison.)

I think this is a forced move: that

    case Box(var o):

be total (including Box(null)).  Any other conclusion is hard to take seriously.  If this excludes Box(null), users will make frequent errors -- because users routinely ignore null's special behaviors (and we mostly want them to keep doing so.)

The next step is a principled one: It's not acceptable for `var` patterns to be inconsistent with type inference.  The choice Scala/C# made here is terrible, that switching between an inferred and manifest type changes the semantics of the match. Super Not OK.  So that means

    case Box(Object o):

has to be total on boxes too.

Another principled choice is that we want the invariant that

    x <matches> P(Q)

and

    x <matches> P(var alpha) && alpha <matches> Q

be equivalent.  The alternative will lead to bad refactoring anomalies and bugs.  (You can consider things like this as being analogous to the monad laws; they're what let you freely refactor forms that users will assume are equivalent.)

Which leads us right to: the pattern `Object o` is total on Object -- including null.  (If `instanceof` or `switch` have a rigid opinion on nullity, we won't get to the part where we try the match, but it can still be a nullable pattern.)

You can make a similar argument for refactoring between if-else chains and switches, or between switch-of-nest and nested switch:

    switch (b) {
        case Box(Frog f): ...      <-- partial
        case Box(Object o): ...    <-- total
    }

should be equivalent to

    switch (b) {
        case Box(var x):
            switch (x) {
                case Frog f:
                case Object o:     <-- must match null, otherwise refactoring is invalid
            }
    }

But if the inner switch throws NPE, our refactoring is broken. Sharp edge, user is bleeding.

You're saying that the language is better without asking users to reason about complex rules about nullity.  But the cost of this is sharp edges when refactoring (swap var for manifest type, swap instanceof chain for switch, swap nested pattern switch for switch of nested pattern), and user surprises.  The complexity isn't gone, its just moved to where we we don't talk about it, but it is still waiting there to cut your fingers.

The totality rule is grounded in principle, and leads to the "obvious" answers in the most important cases.  Yes, it's a little more complicated.  But the alternative just distributes the complication around the room, like the shards of a broken window no one has bothered to clean up.



On 8/6/2020 5:35 PM, fo...@univ-mlv.fr wrote:
I've re-read the proposed spec about the nullable switch,
and it still don't understand why people will think that that the following switch is a nullable switch
  switch(o) {
    case String s ->
    case Object o ->
  }

It's not clear that they have to.  Right now, switches throw hard on null, and yet the world is not full of NPEs that come from switches.  So most users don't even consider null when writing switches, and somehow it all works out.  The last thing we want to do is grab them by the collar, shake them, and say "you are a bad programmer, not thinking about nulls in switch!  You must start obsessing over it immediately!"  Instead, if they're in blissful ignorance now, let's let them stay there.  Let's make the idioms do the obvious thing (Box(var x) matches all boxes), and then, for the 1% of users who find they need to reason about nulls, give them rules they can understand.


Reply via email to