[External] : Re: Patterns: declaration

Brian Goetz brian.goetz at oracle.com
Sat Jan 23 17:16:14 UTC 2021


I was glad to see that Tagir’s exploration mostly visited points I’d already explored.  Of these, the one I was most uncomfortable with, and which I think we should put our attention on first, is the declaration of the target in static patterns, as this removes an asymmetry between the kinds of patterns.  The most credible alternative I’ve found is to treat the declaration of the target _type_ as a usually-inferred type parameter, which for dtor/instance patterns is inferred as the receiver type, but which must be witnessed for static patterns:

    static<T> pattern<Optional<T>>(T t) of() { … }
                      ^ target type

I think this is pretty readable: it says “I’m a pattern targeted at Optional<T>, which binds a T”.  The main challenge is: what do we call the target in the pattern body.  For instance/dtor patterns, we can use `this`, but this is a pretty big stretch for static patterns.  What do we think of picking a reserved identifier for the pattern target, such as “target” or “matchee” or “that” or “operand” or … ?  (Let’s discuss the concept first before we get too wrapped up in the name.)  

Cons:
 - makes the pattern declaration even fussier
 - Not clear that allowing instance patterns to nominate a different target type is useful, even if uniform

Pros:
 - Eliminates magic treatment of the first parameter (compiler must require it be there for static patterns)
 - Eliminates the asymmetry between “declaration has N parameters but use site has N-1”
 - Seems pretty readable
 - Patterns will only have input parameter declarations when there are real input parameters




> On Jan 22, 2021, at 12:57 PM, Brian Goetz <brian.goetz at oracle.com> wrote:
> 
> 
>> Wow, it's bikeshedding time!
> 
> Yep.  I've painted this particular shed several hundred times already, so I've been round the block on most of these.
> 
>> Two points.
>> 
>> 1. I worry about the asymmetry between pattern-with-args declaration
>> and invocation. See:
>> 
>> static pattern(int comp1, int comp2) foo(Type t, int param1, int param2) {...}
>> 
>> if(t instanceof foo(param1, param2)(int comp1, int comp2)) {...}
>> 
>> See, at the declaration, pattern parameters are declared after the
>> components while at invocation parameters are listed before the
>> components.
> 
> Yes.  Both ways have their problems; this is a pick-your-asymmetry. In the end, it felt that:
> 
>  - Putting the bindings near "pattern" and the input args near the method name was more consistent;
>  - At the use site, having the input args come first is important; if you squint, you could almost imagine that in
> 
>     case Map.withMapping(k)(var x)
> 
> the "Map.withMapping(k)" is the pattern, and (var x) is the binding list for it.
> 
>  - That a deconstructor is a degenerate case of a pattern (no name, no input arg list) strengthens the model.
> 
>> Also, having a target parameter as a part of the normal parameter list
>> could be confusing. An alternative idea is to list everything at
>> declaration in the same order as at invocation site. For example:
>> 
>> static pattern Type foo(int param1, int param2) -> (int comp1, int comp2) {...}
> 
> Heh, I explored this one too.  This has the advantage of describing inputs and outputs more clearly, but doesn't really connect with anything else in the language.
> 
>> // it's not strictly required to give a name for the target parameter;
>> it could be referenced inside the pattern body via a specific keyword,
>> probably even 'this'!
>> 
>> class Optional<T> {
>>     static<T> Optional<T> of(T t) { ... }
>>     static<T> pattern Optional<T> of() -> (T t) { ... }
>>     static<T> Optional<T> empty() { ... }
>>     static<T> pattern Optional<T> empty() -> () { ... }
>> }
> 
> Yes, I've agonized about this one too.   One of the lumps in the current proposal is that the target declares like an ordinary parameter, but it is pretty magic (and the compiler will require that it be present.)  I think calling it `this` is a little too weird, but maybe we could get over that.  Or we could have another magic name (target, that, matchee), and then we could move the type out of the arg list, such as:
> 
>     static<T> pattern<Optional<T>>(T t) of() { ... }
> 
> and then we (a) don't have the confusion between the target and an argument, and (b) the argument list really is the argument list, not one bigger than the argument list.  This seems like a workable possibility.
> 
>> static pattern String regex(String regex) -> (String... groups) {...}
>> 
>> interface Map {
>>     pattern withMapping(K key) -> (V value);
>> }
>> 
>> class Class<T> {
>>     public pattern arrayClass() -> (Class<?> componentType) {...}
>> }
>> 
>> Another possibility is to use another type of brackets for pattern
>> parameters, let it be []
> 
> Been there too :)  I liked this because this is how TeX deals with optional parameters (there's a required and optional parameter list).  Most people I showed this one to hated it, though.  (Not many people are TeX-heads.)
> 
>> Other syntactic options are possible, but in general, it looks more
>> natural to me to specify output components on the right side of
>> pattern parameters.
> 
> Of these, the one that seems most significant is finding a different way to name the target, so we can get the target out of the argument list.
> 
>> 2. effectively final expression
>> 
>>> We would likely want to enforce the requirement that expressions used as input
>> arguments be _effectively final_, to eliminate any confusion about the timing
>> of when they are evaluated.
>> 
>> I'm not sure I understand what is 'effectively final expression'. We
>> know what is 'effectively final variable'. Is it possible, say, to
>> call a method at the pattern invocation, like `case
>> regex(buildRegex())(String s1, String s2) -> ...`?
>> 
> 
> An effectively final expression is one for which all the inputs are effectively final; this basically rules out method calls (until we do the cool constant folding stuff.)  We could narrow this to "effectively final variable" if we didn't want to go through that level of detail.
> 
> In my head (maybe not elsewhere), a pattern is like a lambda; the "code" is constant, and it is possibly parameterized by captured variables.  Imagine that there were a first-class type called Pattern.  (Well, there is, but I mean one that evaluates to a linguistic pattern, not a compiled regex.)  Then we could conceivably say (not suggesting this, even though Scala lets you):
> 
>     Pattern p = PatternFactory.randomPattern();
>     ...
> 
>     switch (o) {
>         case p: ...
>     }
> 
> While being able to programmatically build patterns might be cool, this seems over the line.  (There's precendent; you can say `case V` today in an int switch, but V must be a compile-time constant.)  A pattern is like a lambda, and so is a pattern switch; you want the "body" of it to remain constant (among other reasons, so you can compile it efficiently, once) even if it is parameterized by some captured variables.
> 
> Given the argument above where everything to the left of the *second* open-paren is a pattern:
> 
>     case Map.withMapping(k)(var v):
> 
> we are saying that, within a given scope, the semantics of the switch are fixed and can be compiled eagerly or lazily and it won't make a difference to execution.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20210123/62477db2/attachment-0001.htm>


More information about the amber-spec-experts mailing list