> On Jan 24, 2021, at 1:16 PM, Brian Goetz <[email protected]> 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. :-)