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