[patterns] Nullability in patterns, and pattern-aware constructs (again)

John Rose john.r.rose at oracle.com
Fri Jan 10 03:53:25 UTC 2020

On Jan 9, 2020, at 7:04 PM, Brian Goetz <brian.goetz at 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

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!

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

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.

More information about the amber-spec-experts mailing list