Next up for patterns: type patterns in switch
Guy Steele
guy.steele at oracle.com
Wed Aug 12 16:29:17 UTC 2020
And now that I have posed the model below, I can spot at least two ways in which the model is wrong in its details. But I think its structure provides a useful framework for discussion. I will try to produce a corrected version late today.
—Guy
> On Aug 12, 2020, at 12:20 PM, Guy Steele <guy.steele at oracle.com> wrote:
>
>> On Aug 12, 2020, at 9:45 AM, forax at univ-mlv.fr wrote:
>> . . .
>> yep, i'm hapy with "default P", let me explain why :
>>
>> I see the switch semantics as a kind of compact way to represent a cascade of if-else,
>> with that they are several pattern
>> case Constant is equivalent to if (o.equals(Constant)) or if (o == Constant)
>> case null is equivalent to if (o == null)
>> case P p is equivalent of if (o instanceof P p)
>> and
>> default P p is equivalent to else { P p = o; }
>>
>> some patterns accept null (case null and default P), so if one of them is present in a switch, the switch accept null, otherwise it does not.
>>
>> so the following code
>> if (o instanceof Frog f) { ... }
>> else if (o instanceof Chocolate c) { ... }
>> else { var x = o; ... }
>> can be refactored to
>> switch(o) {
>> case Frog f: ...
>> case Chocolate c: ...
>> default var x: ...
>> }
>>
>> About exhaustiveness, if a switch is exhaustive, by example with
>> sealed interface Container permits Box, Bag { }
>> the following switch is exhaustive
>> switch(container) {
>> case Box box: ...
>> case Bag bag: ...
>> }
>>
>> Here there is no need for a total pattern and if a user want to allow null, he can add a "case null".
>>
>>> Now, what is the story for nested patterns?
>>>
>>> switch (container) {
>>> case Box(Frog f): ...
>>> case Box(Chocolate c): ...
>>> case Box(var x): ....
>>>
>>> case Bag(Frog f): ...
>>> case Bag(Chocolate c): ...
>>> case Bag(var x): ....
>>>
>>> }
>>
>> so this is a mix between an exhaustive switch with two total patterns once de-constructed, for me, it should be written like this
>> switch(container) {
>> case Box(Frog g): ...
>> case Box(Chocolate c): ...
>> default Box(var x): ...
>>
>> case Bag(Frog g): ...
>> case Bag(Chocolate c): ...
>> default Bag(var x): ...
>> }
>>
>> using the syntax "default Box(var x)" to say that the nested-patterns are locally total thus accept null.
>> It's a little weird to have the "default" in front of the type name while it applies on the nested part but i'm Ok with that.
>
> Very interesting proposal, to allow more than one “default” clause in a switch!
>
> I’m not yet sure whether I like this path, but I want to explore it further, and to do that I will attempt to formalize it a bit more and then look at a more detailed example.
>
> Remi suggested these rules:
>
> `case Constant` is equivalent to `if (o.equals(Constant))` or `if (o == Constant)`
> `case null` is equivalent to `if (o == null)`
> `case P p` is equivalent to `if (o instanceof P p)`
> `default P p` is equivalent to `else { P p = o; }`
>
> I think that to get the desired effect in the last example, it is necessary to be more detailed, and distinguish various kinds of patterns (let T stand for a type, let P: Pattern T, let Q be any pattern, and let S be a statement):
>
> `case X` is equivalent to `if (CASE_EXPAND(o, X))`
> `default X` is equivalent to `if (DEFAULT_EXPAND(o, X))` (**) we will refer to this rule later
> `default` is equivalent to `default var unused_variable`
>
> `CASE_EXPAND(o, Constant)` is equivalent to `o.equals(Constant)` or `(o == Constant)`
> `CASE_EXPAND(o, null)` is equivalent to `(o == null)`
> `CASE_EXPAND(o, T p)` is equivalent to `o instanceof T p`
> `CASE_EXPAND(o, var p)` is equivalent to `o instanceof var p` [just a way to bind p to the value of o in the middle of an expression]
> `CASE_EXPAND(o, P(Q))` is equivalent to `o instanceof P(T alpha) && CASE_EXPAND(alpha, Q)`
>
> `DEFAULT_EXPAND(o, Constant)` is a static error?
> `DEFAULT_EXPAND(o, null)` is a static error?
> `DEFAULT_EXPAND(o, T p)` is equivalent to `{ T p = o; }`
> `DEFAULT_EXPAND(o, var p)` is equivalent to `{ var p = o; }`
> `DEFAULT_EXPAND(o, P(Q))` is equivalent to `o instanceof P(T alpha) && DEFAULT_EXPAND(alpha, Q)`
>
> where by an abuse of notation I write `{ T p = o; }` for an “expression” that checks to see whether the value of o is assignable to type T, and if it is then binds the variable p to that value produces the value true, and otherwise produces false. (In other words, it is like `o instanceof T p` but accepts nulls.)
>
> I am assuming that `o instanceof P(T alpha)` is null-friendly (it always allows the possibility that alpha may be bound to null).
>
> (And I note that I have been sloppy about how the cases and their associated statements are glued together to make a complete translation of a switch statement.)
>
>
> Now let’s examine this extended example (assume `record Box(Object f)` and `record Bag(Object f)` and record `FrogBox(Frog f)`):
>
> switch(o) {
> case Box(Frog g): ...
> case Box(Chocolate c): ...
> default Box(var x): ...
>
> case Bag(Frog g): ...
> case Bag(Chocolate c): ...
> default Bag(Object x): … // I changed `var x` to `Object x` here
>
> case FrogBox(Toad t): …
> case FrogBox(Tadpole tp): ...
> default FrogBox(Frog fr): …
>
> default: ...
> }
>
> This would expand to something like:
>
> if (o instanceof Box(Object alpha) && alpha instanceof Frog g) …
> else if (o instanceof Box(Object alpha) && alpha instanceof Chocolate c) …
> else if (o instanceof Box(Object alpha) && { var x = alpha; }) …
>
> else if (o instanceof Bag(Object alpha) && alpha instanceof Frog g) …
> else if (o instanceof Bag(Object alpha) && alpha instanceof Chocolate c) …
> else if (o instanceof Bag(Object alpha) && { Object o = alpha; }) …
>
> else if (o instanceof FrogBox(T alpha) && alpha instanceof Toad t) …
> else if (o instanceof Box(T alpha) && alpha instanceof Tadpole tp) …
> else if (o instanceof Box(T alpha) && { Frog fr = alpha; }) …
>
> else …
>
>
> But now I realize that this model is not quite what we had discussed before: I think I need to change one of the rules above (**) to three rules:
>
> `default T p` is equivalent to `T p = o; if (true)`
> `default var p` is equivalent to `var p = o; if (true)`
> `default P(Q)` is equivalent to `if (DEFAULT_EXPAND(o, P(Q)))`
>
> That is, at “top level”, the situations `default T p` and `default var p` are not conditional, but are required to succeed, and you get a static error for the first one if o is not assignable to T. This may be ugly, but at least it reveals explicitly that we are treating the outermost situation in a `default` label a bit differently from nested situations.
>
>
> Does this model capture the intent of what everyone wants?
>
> —Guy
>
More information about the amber-spec-experts
mailing list