The good and the bad static pattern ?

Brian Goetz brian.goetz at oracle.com
Mon Jan 18 16:09:53 UTC 2021


Seems you’re asking “why have static patterns, when all patterns have a target anyway”.

Let’s back up and ask “what couldn’t we do if Java didn’t have static methods.”  There are a number of things we could not express:

 - Methods whose arguments and return types are primitives, such as Math.max().  We cannot recast these as constructor or instance methods because primitives do not have these.  We’d have to instead express these as instance methods on boxes.  
 - Static factories.  While we could live in a world without static factories, it is a useful pattern, and EJ rightly tells us why we should prefer them to constructors.  Without static methods, we’d just have to use constructors.
 - “Outboard” functionality.  If String didn’t provide a reverse() method, you could write your own: MyStringUtils.reverse(String).  

In some cases, we have no choice between instance and static, but in many cases, we do; either choice for reversing a string would have been acceptable. This is one of the degrees of freedom that Java developers are used to, and have learned to navigate.  

While there are obviously other ways to get “static” functionality without the keyword “static” (e.g., Scala’s “object” idiom), I think we can agree these are all tools we find useful, even if static-ness has lots of warts and sharp edges.  

One of the cornerstones of the design proposed for patterns is that destructuring is the dual of aggregation, and that how we express destructuring should have similar syntax and semantics to how we express aggregation.  If we express aggregation with a constructor, we should have a anti-constructor-like pattern:

    Foo f = new Foo(x);
    if (f instanceof Foo(var x)) { … }

(This duality is not mere syntactic trick; it appeals to a powerful mathematical concept called _adjunction_, and the consequences of this are, in turn,  an enabler for things like reconstruction.)  

If we express aggregation with a factory, though, we would like a “unfactory”-like notion of destructuring: 

    Foo f = Foo.of(x);
    if (f instanceof Foo.of(var x)) { … }

Could we get away with saying that this is unnecessary?  Well, we could allow static factories but not static patterns, but for all the reasons Josh says “prefer factories to constructors”, we can make many of the same arguments for static patterns.  Switching over an Optional this way would suck:

    case Optional(var value):
    case Optional(): // empty

There’s a reason we decided not to expose constructors for Optional shaped like this.  

Additionally, as Tagir points out, static methods are powerful for letting us add “extension” functionality.  If Foo doesn’t provide a constructor for a given configuration of Foo, but that configuration is otherwise constructible anyway, anyone can write a factory for “makeFooWithCheeseAndMustard”.  If you can write a factory to aggregate, you may reasonably want to write a corresponding pattern to destructure.  


> One problem is that while it's obvious that the first pattern starts by checking if o is an instanceof String,
> it's far less clear from a user POV that the second pattern does exactly the same thing and does not check if o is an instance of Integer.

I think you’re asking: how does a user know that a pattern is applicable to the target?  And the answer is: just like methods, we look at the declaration.  For instance patterns, the target is the same type as the class its declared in; for static patterns, the type is explicit in the declaration.  



More information about the amber-spec-experts mailing list