[External] : Re: Patterns: declaration

Brian Goetz brian.goetz at oracle.com
Fri Jan 22 17:57:47 UTC 2021


> 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.


More information about the amber-spec-experts mailing list