> 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