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