Patterns: declaration

Guy Steele guy.steele at oracle.com
Mon Jan 25 22:37:56 UTC 2021


> On Jan 24, 2021, at 1:16 PM, Brian Goetz <brian.goetz at oracle.com> wrote:
>> 
>> Hi, Brian,
>> 
>> This exploration of the different ways of declaring patterns is very useful, as
>> if the observation that for every sort of constructor or method, there can be a
>> corresponding sort of pattern.
>> 
>> I believe, however, that it then falls prey to a fallacy: I believe that it is
>> not correct to conclude, just because every sort of constructor or method has a
>> corresponding form of pattern, that for every sort of constructor or method the
>> corresponding form of pattern should be used.
> 
> This is a great point; this is where language design meets library design, and while it may be sensible for the language to have all the options, perhaps the libraries need not follow the path of least resistance.  As a concrete example, let's take Optional.  We have static factories Optional.of(x) and Optional.empty(), which are the _only_ ways to construct Optionals from outside the capsule.  And clearly, we want to capture the conceptual and syntactic symmetry of:
> 
>     Optional<String> o = Optional.of(foo);
>     ...
>     if (o instanceof Optional.of(var foo)) { ... }
> 
> But, it eluded me that the second locution need not appeal to a static pattern, just because the factory is a static method.  If we define:
> 
>     final class Optional<T> {
>         static<T> Optional<T> of(T t) { ... }
>         pattern(T t) of() { ... }
>     }
> 
> then the same use-site syntax refers to the instance pattern here. And if the target is a broader type than Optional, then either would require the same synthetic target-applicability test (is the target an Optional.)
> 
> This is your main point, right?

Yes, exactly so.  (Well, I observed that the use-site syntax _could_ be the same whether the pattern is static or instance, but that there is a bit of a trade-off.  See below.)

> . . .
> Allow me to inject a confounding factor: static imports.

Well, sure.  With respect to the `max` method, the static import feature means I have to write “Math.” only once per compilation unit rather than once per use—but I still resent it.  :-/

> We have static imports for methods, so you can static import Optional.of, and then say `Optional x = new of(y)` if you so desire.

I think you must have meant to say `Optional x = of(y)`, yes?

> So surely, patterns would want the same consideration?

Certainly.

> But, this is a confounding factor, I think; it's a matter of specifying "pattern selection" carefully, and I believe that saying `Qualifier.name(stuff)` vs `name(stuff)` is mostly independent from static-ness.  The former could describe either an instance or static pattern (modulo same problems we already have with methods, if there are instance and static methods with the same name and descriptor), and the latter could either determine the qualifier from the static type of the switch operator, _or_ from the `import static` context. So either form of use could be either form of declaration.
> 
>> We can get this concision for the second example by declaring instance patterns
>> rather than static patterns to complement the static factory methods.
>> Unfortunately, this makes the first example no longer work.
> 
> I think the first example can continue to work even if they are instance patterns?

It can, but only after after agreement that the semantics of the notation will indeed be extended to cover that case.

> We do a member selection for `Optional.of(...)`, discover there is an applicable pattern there, that it is an instance pattern on Optional<T>, do our target-applicability calculation, and we're off to the races?

Whee!

>> Idea (c) is to regard the meaning of a pattern of the form `T.P(...)` as
>> depending on whether the pattern P declared in class T is a static pattern or an
>> instance pattern.  This allows us to use the originally proposed form with dots:
>> 
>> ```
>> switch (myObject) {
>>     case Optional.of(var t): ...
>>     case Optional.empty(): ...
>>     case OptEither.left(var t): ...
>>     case OptEither.right(var u): ...
>>     case OptEither.empty(): ...
>> }
>> ```
> 
> I think (c) is where we're aiming at now (though to be fair, it was only discussed in passing, I think in response to AlanM's recent mail.)
> 
>> at the expense of overloading the notation `T.P`, which some programmers might
>> find disturbing.
> 
> We've already got an analogous overloading, which has actually turned out to be super-popular: method references.  Foo::bar is either (a) a method reference to the static method bar in Foo, or (b) a method reference to the _unbound_ instance method bar in Foo, in which case the receiver is added as an extra first parameter (eta abstraction).  So `String::length` is a method reference that is equivalent to the lambda `(String s) -> s.length()`.  Even though the Javadoc says the length method has no arguments, users don't seem fazed by this at all.  So, doubling down on this seems like a good move.

Okay, if you are not opposed to letting `Optional.of` in a pattern refer to either a static pattern or an instance pattern, depending on what was declared, then I agree that idea (c) is superior to (a) or (b).

>> Bottom line: Static patterns, like static methods, are clunky to use and
>> therefore instance patterns should be used wherever possible, even as
>> complements to static factory methods.
> 
> I am surprised to find that this works so well, but I can find no flaw in your argument!  (But no, Remi, I don't think this obviates the value of having static patterns in the language, it just means we might use them less often.)   Very slick.

I was hoping you would like it.  :-)

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


More information about the amber-spec-experts mailing list