> On Jan 24, 2021, at 1:16 PM, Brian Goetz <brian.go...@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. :-)