I don't want to dive into a design discussion on declared patterns, but I think we can resolve most of the nullability issues without doing so.

Like methods, declared patterns are members.  Like methods, they can be static or instance.

If they are instance, the target is the receiver, and that surely won't work if the receiver is null.  So by definition, instance patterns cannot match null.  Let's set that aside.

(Example: an instance pattern is a pattern on a class, like:

    switch (aString) {
        case palindrome(var firstHalf, boolean isOdd): ...
    }

where palindrome is an instance pattern on String.)

Declared patterns have a target type (may want a better name), which for an instance pattern is the receiver type, and for a static pattern is part of the declaration.  (For example, `Optional.of(var x)` has a target type of `<T> Optional<T>`.)

We won't even try to match the pattern unless the match target is of the target type for the pattern (it's not an Optional.of(anything) if its not an Optional to begin with.)  So we have to do some sort of test first -- either instanceof (in which case static pattern will never match null, period), or a type pattern match (which would be the more principled choice, and would only admit a null through to the user-written code if the target type of the static pattern were total on the operand of the switch/instanceof.)

So it's already a pretty narrow case where a null would be admitted into the user code at all.  Now, the finishing blow: do we really want to ask the authors of the `Optional.of(var x)` and `Optional.empty()` patterns to have to check their target for null?  That would be creating a million new bugs to accommodate a handful of corner cases.

Which brings us back to ...

Only the top (total) and bottom (null constant) patterns match null.  Guy is happy again.  (And the crowd goes wild.)






On 1/9/2020 10:53 PM, John Rose wrote:
On Jan 9, 2020, at 7:04 PM, Brian Goetz <brian.go...@oracle.com> wrote:
I guess that means the stuff I said about "only having to look in two places" 
for null-handling was wrong; any static (declared) pattern could be nullable.  Which 
brings us back to the place that people didn't like the last time around -- you have to 
scrutinize the implementations of the patterns associated with all N cases to determine 
whether this switch will take nulls or not.
Yeah; I wondered if you’d say that.  This is a problem with methods of any
kind, isn’t it?  Programmers must always wonder what’s inside any given
method call.  We have javadoc and @NotNull annotations to help, but it’s
always going to be difficult.

That said, library patterns will surely be composable from (or along side)
more primitive patterns, and we (perhaps) could also require that the
various pieces and parts of a null-friendly library pattern would visibly
“let the null in” to the library code, and failing that the null would be
rejected.

Straw man:  Declare that all static patterns are non-total, and exclude
null from them on the same principle as today’s non-total patterns.
This requires a library pattern to be treated as always non-total.

Straw man:  Allow static patterns to explicitly mention the “receiver”
of the match, as an optional precursor pattern.  Key null-transmission
on whether that precursor pattern is total or not, under today’s rules.
By precursor pattern I mean the occurrence of the pattern “String”
in the following straw man syntaxes:

   // static __Pattern myLibPattern(__MatchTarget Object target, int modes);
   switch (o) {
   case myLibPattern(0x42): …
   case myLibPattern(__MatchTarget String x, 0x42): …
   case String x & myLibPattern(0x42): …
   case (String x).myLibPattern(0x42): …  // this looks *really* null hostile!
   …etc…
   }

This depends sensitively on the concrete design of library patterns, but
I’m assuming the usual connection points of (a) the value being matched,
(b) the outputs to the match (if any), and (c) additional parameters (“modes”).

My overall point here is that there are plausible options to keeping the
null under control, even visibly so.  And we can stack the deck so that letting
in the null is always accompanied by some telltale signal, not just a tacit
default.

I think, when we cross this bridge, we can appeal to some combination
of documentation, good style, and explicit structure in the case pattern.

Here’s another example to suggest some various degrees of freedom,
and some pitfalls:

   static __Pattern <T> ofClass(
         __MatchTarget Object x,
         Class<T> c, boolean nullOK,
         __MatchResult T x2) {
     if (c.isInstance(x))  __Match(x2 = c.cast(x));
     else if (nullOK)  __Match(x2 = null);
     else  __Fail;
   }  // (not the real syntax)

   boolean z = …;
   Object o = …;
   switch (o) {
   case Objects.ofClass(String.class, z, var s): return 1;  //worst case
   case Objects.ofClass(Number.class, true, var n): return 2;  //RTFJD
   case Objects.ofClass(List.class, false, var x): return 3;  //ditto
   default: return 0;  //NPE here???
   }

On 1/9/2020 7:57 PM, John Rose wrote:
We haven’t yet moved on to library-defined patterns (“static
patterns”).  To gaze a bit into the crystal ball:  I think it’s
likely that those will provide a way to express T? using
a library API instead of a language primitive.  We should
defer building out that last 1% of null support either
indefinitely, or until there is a way for libraries to define
their own kinds of patterns.  In the latter case the cost
of adding a nullable pattern is likely to be lower than
the alternatives on the table, since library changes are
always cheaper than language changes.

Reply via email to