Patterns: declaration

Brian Goetz brian.goetz at oracle.com
Sun Jan 24 18:16:06 UTC 2021



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

> There is an inherent asymmetry between a static factory method and a 
> pattern:
> the static method has no target, whereas a pattern (whether static or 
> instance)
> _always_ has a target.

Yes, this is an unfortunate asymmetry, because it forces the language to 
give special meaning to a parameter.  (The proposed alternate syntax 
that Tagir and I discussed hide this somewhat, but its still there.)

> I have long felt that static methods in Java are a kind of 
> second-class kludge.

John frequently says: "static has messed up every job we've given it; 
don't give it more jobs to mess up."

> For the first example, this produces code that looks pretty good, 
> because it is
> obviously necessary to indicate a type in addition to the method names:
>
> ```
> switch (myObject) {
>     case Optional.of(var t): ...
>     case Optional.empty(): ...
>     case OptEither.left(var t): ...
>     case OptEither.right(var u): ...
>     case OptEither.empty(): ...
> }
> ```

Allow me to inject a confounding factor: static imports.  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.  So surely, patterns would 
want the same consideration?

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

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

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




More information about the amber-spec-experts mailing list