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.


Reply via email to