> On Aug 12, 2020, at 9:45 AM, [email protected] 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

Reply via email to