<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</head>
<body bgcolor="#FFFFFF" text="#000000">
<div class="moz-text-flowed" style="font-family: -moz-fixed;
font-size: 14px;" lang="x-unicode">I would like to start the
discussion surrounding pattern matching with some motivation and
goals. The design space here is enormous, and connected to so
many other features, so bear with me as I try to linearize the
story. I've posted a general introductory document on
possibilities for pattern matching in Java here:
<br>
<br>
<a class="moz-txt-link-freetext"
href="http://cr.openjdk.java.net/%7Ebriangoetz/amber/pattern-match.html">http://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html</a>
<br>
<br>
The first question is why, as in: Why is this feature needed in
Java at all?
<br>
<br>
I'll start with the obvious reasons (explicated in greater detail
in the document): the common pattern of testing an input against
multiple candidates not only suffers from boilerplate, but, more
importantly, the tools we have for this give bugs too many places
to hide, obfuscate the essential business logic, and introduce
accidental ordering constraints that make programs slower than
necessary.
<br>
<br>
There are also some less obvious reasons why this makes sense.
What's being proposed here are really two interrelated features
here: better support for compositional test-and-extract
(destructuring), and better support for handling
do-exactly-one-of-these constructs. While either could stand on
its own, the two together are much stronger. Of the two,
destructuring is the richer concept.
<br>
<br>
To illustrate why destructuring is so important, allow me to make
a comparison to Lambda. Adding the ability to pass behavior as
data meant that we could build richer, more powerful libraries.Â
We could move responsibility for control flow from client code
(external iteration) to libraries (internal iteration), enabling
libraries to expose richer, higher-level operations.Â
Additionally, by exposing higher-level operations, libraries might
not have to expose fiddly, state-dependent low-level operations
like Iterator.hasNext() / next(), and therefore don't need to code
iteration logic against being called in the wrong order or in the
wrong state.
<br>
<br>
Destructuring has a similar abstraction-raising characteristic.
Consider the methods Class.isArray() and Class.getComponentType();
the latter only returns a value if the former returns true.Â
Method pairs like this offer the worst of both worlds; the client
has to make two calls, and has a chance to do it wrong -- and the
library has to defend against the client getting it wrong. These
two methods really should be one operation, which simplifies both
the client invocation and the library implementation:
<br>
<br>
   if (c matches Class.arrayClass(Class<?> componentType))
{ ... }
<br>
<br>
Further, decomposition (when done right), is compositional,
enabling us to express a compound test+extract operation as a
single operation, rather than as a sequence of operations, each of
which can individually fail. This brings client code more in line
with how the problem statement is typically stated.
<br>
<br>
Finally, this is a feature with depth; we can start with simple
patterns and simple pattern-aware language constructs, and add
more sophisticated kinds of patterns and more constructs that can
use patterns as we go.Â
This in turn means a greater return on conceptual complexity.<br>
<br>
<br>
If one looked only at the first few examples in the document, one
might be tempted to ask "Why not 'just' do flow typing"? Flow
typing would eliminate the (irritating) need to cast something to
X right after having done an instanceof test against X. It seems
like a cheap win (though would invariably put pressure on our
handling of intersection types), but it just doesn't go very far
-- essentially it eliminates a single increment of boilerplate and
a single place where error can creep in. This is not nothing, and
it's a defensible choice, but it seems like it would be a missed
opportunity.
<br>
<br>
Similarly, one might ask "Why not 'just' do type switch?". Again,
this is a feature that seems merely "additive" rather than
multiplicative; it again eliminates some boilerplate in repeated
type tests, but it doesn't go much farther than that.
<br>
<br>
<br>
Which brings me around to a deeper observation, which we think is
at the design center of this feature: destructuring is the natural
dual of aggregation, and an obvious (and yet missing) component of
object-oriented modeling.
<br>
<br>
People often associate pattern-matching as being a "functional
programming" feature. But any language that supports aggregation
(that is, all of them) also has to address how aggregates are
going to be decomposed. Scala moved the ball forward here by
showing that having a destructuring operator ("unapply") on
objects allows us to match and destructure objects in terms of
what their constructor invocation looks like (regardless of their
representation.)Â This is not "OO borrows FP idea", this is "OO
applies sensible programming concept in a natural OO way."
<br>
<br>
That said, I think Scala didn't go far enough. (No criticism
intended; they moved the ball forward dramatically, there's just
farther to go.)Â For what are likely mostly accidental reasons,
Scala only allows a single constructor pattern per class, even
though constructors themselves can be overloaded.
<br>
<br>
Again, without criticism: I'm guessing most of the motivation for
Scala's pattern matching was drawn from the distinguished and
well-behaved case of algebraic data types, not general objects.Â
So the limitation of a single unapply was not really a problem;
algebraic data types don't go out of their way to hide or evolve
their representation. But we can apply destructuring to a wider
range of targets, in a more OO way. <br>
<br>
So, to put a stake in the ground, we believe that:
<br>
 - destructuring is the dual of construction, and should be
treated as a first-class construct;
<br>
 - destructuring an object should be syntactically similar to how
that object is constructed.
<br>
<br>
From these principles, it follows that:
<br>
 - if you can declare a ctor, you should be able to declare an
instance dtor;
<br>
 - instance dtors should support the same overloading and
inheritance behaviors as ctors;
<br>
 - if you can declare a static factory, you should be able to
declare a static dtor.
<br>
<br>
(The syntax of how dtors are declared is a topic for another day.)
<br>
<br>
If an object is created with a constructor:
<br>
<br>
   x = new Foo(a, b)
<br>
<br>
ideally, it should be destructurable with an instance dtor:
<br>
<br>
   if (x matches Foo(var a, var b))
<br>
<br>
Note the syntactic similarity; this is not accidental. Acquiring
dtors isn't automatic (though data classes can automatically
acquire both ctors and dtors), but it should be easy and
straightforward to declare such a dtor.
<br>
<br>
If dtors are overloaded, type information at the use site may be
needed to disambiguate. So if we provide dtors Foo(int y) and
Foo(String y), then
<br>
<br>
   if (x matches Foo(var y))
<br>
<br>
is ambiguous, but by replacing the var-pattern with a an explicit
type-test pattern, the compiler can properly select:
<br>
<br>
   if (x matches Foo(int x))
<br>
<br>
Similarly, if an object is created with a static factory:
<br>
<br>
   x = Foo.of(a, b)
<br>
<br>
it should be destructurable with static dtors:
<br>
<br>
   if (x matches Foo.of(var a, var b))
<br>
<br>
So, for example, since Optional has two factories:
<br>
<br>
   x = Optional.of(y)
<br>
   x = Optional.empty()
<br>
<br>
one can discriminate via:
<br>
<br>
   case Optional.of(var y):
<br>
   case Optional.empty():
<br>
<br>
<br>
Why does this matter? By way of example, we often prefer factory
methods to constructors. A factory has more flexibility than a
ctor; it can return different subtypes in different conditions,
and it can change its implementation over time to return different
subtypes. That a factory commits only to an abstract type:
<br>
<br>
   static Foo redFoo() {
<br>
       return new MyPrivateFooImplementation();
<br>
   }
<br>
<br>
is great for the implementor, but less so for the client -- once
the caller gets a Foo from the factory, it has no way of asking
"is this a red Foo?", unless we clutter the API with ad-hoc
methods like isRedFoo(). This means that APIs with multiple
factories have some bad choices: either live with the opacity,
which can limit what the client can do (sometimes this is good,
but sometimes it's not), add lots of new API points for
destructuring, or expose the intermediate types.
<br>
<br>
Having a complementary dtor allows the implementation to hide its
details while still providing a degree of reversibility. Without
venturing near the toxic syntax bikeshed:
<br>
<br>
  static ... matcher ... Foo ... redFoo(...) { ... }
<br>
<br>
the library author can render the factory reversible without
exposing the implementation details.
<br>
<br>
   x = redFoo();
<br>
   ...
<br>
<br>
   if (x matches redFoo()) { ... }
<br>
<br>
The implementation of redFoo() remains hidden, but because the
dtor is part of the same library as the factory, it can still help
a client reconstruct the object's state (or the parts that it
wants to), without compromising the encapsulation. (In just the
last few weeks of thinking about this, once you start to see this
pattern, you can't look at an API and not see it.)<br>
<br>
<br>
Again, this is mostly something to file in the background and
think about the consequences of -- until we start to circulate a
JEP for pattern matching, this is still technically outside the
Amber charter. <br>
<br>
<br>
</div>
</body>
</html>