Nullity (was: Pattern features for next iteration)

Remi Forax forax at univ-mlv.fr
Fri Jan 22 13:50:30 UTC 2021


I like the  special bonus semantics,
a switch need to specify the null behavior explicitly while it's pragmatic to lees sub-pattern to be total on null.

Because people will ask, if a record has a null record component, the "Right Way"(TM) to not propagate null is to create different patterns like Optional (Optional$$of()/Optional$$empty()) to avoid to bind null.

I don't think we should support the fallthrough with null given that adding a "null comma" in front of a case provides the same semantics.

Rémi

----- Mail original -----
> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
> Envoyé: Jeudi 21 Janvier 2021 19:52:39
> Objet: Nullity (was: Pattern features for next iteration)

>>    - New nullity behavior (including case null)
> 
> I think we can refine this item a bit, now that we've made some progress
> on guards.  There are still some mumbles of discomfort regarding the
> treatment of nulls.   We are never going to quiet those, because nulls
> are a persistent source of thorns, but I think we can add something new
> to the discussion in light of recent progress.  (But please, let's not
> just recycle old arguments.)
> 
> There are three contexts so far where we can put patterns:
> 
>  - RHS of an instanceof
>  - case label of a switch
>  - Nested inside a record or array pattern
> 
> Some of these have strong opinions on nulls, that might cause action to
> be taken before the pattern is even tested.  This is fine.  But we
> should be clear about which behaviors are part of the _construct_, vs
> which are part of pattern matching.  Currently:
> 
>  - instanceof always says false on null, no matter what
>  - switch throws on null, except under circumstances we are defining now
>  - nesting has no null opinions, but when the inner pattern requires a
> dynamic test (i.e., is not total), that pattern may have an opinion
> 
> With regard to what it means for "Pattern P to match target X whose
> static type is T", previous rounds came to a pretty solid conclusion
> that `var x` and `Object o` _must_ match null.    (So please, let's not
> reopen that unless there is something significantly new to add.)
> 
> What is missing is: when `Object o` in some context matches null, how do
> we express that we actually wanted to exclude null?  We've explored in
> the past some sort of `Object! o` type pattern, but resisted this for
> obvious stewardship reasons.  But the goal is valid: the pattern matches
> too much, and we want to refine the match.  And, this is what pattern
> composition does, so we should be looking to pattern composition to
> solve that.
> 
> Here's what is new: the treatment of guards as composable patterns. With
> that, we can write a "non-nullable" nested pattern like:
> 
>     case Foo(Object o & false(o == null)):
> 
> or
> 
>     case Foo(Object o) & false(o == null):
> 
> (depending on where the user thinks the null check is better.)  What I
> like here is that we haven't distorted the meaning of patterns to handle
> null specially, but instead are using ordinary composition mechanisms to
> allow users to filter nulls just like any other bad value.
> 
> If it turns out, that the world becomes full of such locutions, we can
> consider (in the future) adding a "null guard" pattern, `null(o)` and
> `non-null(o)`, at which point the above becomes:
> 
>     case Foo(Object o & non-null(o)):   // guard flavor
> 
> or
> 
>     case Foo(Object o & non-null()):       // targeted flavor
> 
> but we surely don't have to do that now, as this is just a trivial
> syntactic shortcut.
> 
> So, not only don't I think we have to add anything new now for handling
> nulls, or further distort the semantics of matching, but I see this as a
> dramatic validation of the guards-as-patterns strategy.  (Who knew
> composition was powerful!)
> 
> 
> #### Special bonus nullity discussion
> 
> Separately, another possibility for switch is to slightly refine the
> null-handling of the switch _construct_ (not patterns), in a way that
> some people might find less surprising.
> 
> Problem: Switch has always been null-hostile, and pattern totality is
> subtle.  People seem very afraid (more than I think they should be, but
> fine) that the nulls will creep into the total patterns without warning.
> 
> Currently, we've defined that switch is null-hostile _unless_ there is a
> nullable case, and the two nullable cases are `case null` and `case
> <total pattern>`.  Obviously no one will be surprised to see a null
> match `case null`, but they might be surprised at the latter.
> 
> So the alternative idea to explore is: make `case null` the _only_
> nullable case, but relax fallthrough to allow falling through from `case
> null` to a type pattern, and adjust the binding rules to make this make
> sense.  Then, a switch is only nullable if there is a case null (easy to
> spot) and a type test only sees null if the `case null` is highly proximate:
> 
> ```
> switch (o) {
>     case Object o: // still NPEs on null, since no case null
> }
> 
> switch (o) {
>     case null -> doSomething();  // nulls here
>     case Object o -> doSomethingElse(); // no nulls, already handled
> }
> 
> switch (o) {
>     case null:  // fall through
>     case Object o: // nulls fall into this case, and are bound to o
>         doSomething(o);
> }
> ```
> 
> As a bonus, it works not just for total type patterns, but lets us sort
> the nulls into any type pattern:
> 
> ```
> switch (o) {
>     case String s -> ...
>     case null, Integer i -> ...   // nulls go here
>     case Object o -> ...
> ```
> 
> I'm OK with this because it seems a reasonable accommodation for the
> historical null-hostility of switch, but doesn't affect the semantics of
> patterns at all.


More information about the amber-spec-observers mailing list