Reiterate total pattern accepting null in switch

Tagir Valeev amaembo at gmail.com
Wed Sep 8 05:15:19 UTC 2021


Hello!

>      case Object! o:   // binding, no null
>      case Object? o:   // binding, null
>      default:          // no binding, no null
>      null, default:    // no binding, null
>
> So the first thought experiment I am asking you to do is whether, in
> this world, you would feel significantly differently.

This is definitely more clear, at least you have a visual sign that
null is allowed there, and don't need any bigger context. Also, Kotlin
programming experience helps here.
However, this will allow introducing several null-friendly branches:

case String? s -> ...
case Number? n -> ...

Now, we can do this with
case null, String s -> ...
case null, Number n -> ...
And it's much more clear that something is wrong (and compiler error
will be perfectly clear). When using the question mark, it's not clear
whether we should prohibit this (as both cases are actually
populated). In other words, this syntax would introduce its own
problems. Though probably, it's justified to explore this syntax
again, as we are approaching nested patterns. I'm not sure whether we
need `Object!` though, just `Object` (non-null by default) would be
enough.

Also, having "String? s" pattern would allow enhancing instanceof to
match null. Sometimes, it's really useful. In IntelliJ sources, we
have 388 conditions like `$x$ == null || $x$ instanceof $t$`. Well, in
most of the cases, the binding variable is unnecessary (it's usually
like if(x == null || x instanceof SomeWrongType) return false)

> To reiterate the motivation, the thing we're going for here is the
> consistency that a switch of nested patterns and a nested switch are the
> same:
>
>      case Box(Foo f):
>      case Box(Object o):
>
> is the same as
>
>      case Box b:
>          switch (b.get()) {
>              case Foo f:
>              case Object o:
>          }
>      }

Yes, I remember that the motivation was like this.

> If we treat the null at the top level, we get a different kind of
> asymmetry.  What we're banking on (which could be wrong) is that its
> better to rip off the band-aid rather than cater to legacy assumptions
> about switch.
>
> I think what you are saying here is that switch is so weird that it is
> just a matter of pick your asymmetry, and the argument for moving it to
> the top level is that this is weirdness people are already used to.

Yes, exactly!

> This may be true, though I worry that it is just that people are not
> *yet* used to nested switches, but they'll be annoyed when they get bit
> by refactoring issues.

Probably you're right.

> OK, but write those cases out nested one level in Box(); are you not
> equally unhappy there?

It's interesting. Let's assume we have Box<String>, so String s is
total on the box component. Am I correct in the interpretation of the
following patterns?

case Box(String s && s.isEmpty()) // String s && s.isEmpty() is not
total on the box component, so Box(null) is not matched here
case Box(String s) && s.isEmpty() // String s is total on the box
component, so Box(null) is matched in nested pattern, and s.isEmpty()
will produce NPE

If yes, then yes, it's confusing. Here, having String? (to allow
nulls) and String (to disallow nulls regardless of totality) would
definitely make things more clear.

With best regards,
Tagir Valeev


More information about the amber-spec-experts mailing list