<html><body><div style="font-family: arial, helvetica, sans-serif; font-size: 12pt; color: #000000"><div>Hello,<br></div><div>I think it is also interesting to instead of starting from deconstruction and then trying to expand, to do in the other way, starts with a pattern backed by a method and then see the deconstruction as a special case of a pattern backend by a method. So instead of using a top-down approach, try to use a bottom-up approach.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>Let's take Optional as first example, Optional is defined like this<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>public final class Optional<T> {<br> private T value;<br><br> private Optional(T value) {<br> this.value = value;<br> }<br><br> public static <T> Optional<T> empty() {<br> return new Optional<>(null);<br> }<br> public static <T> Optional<T> of(T value) {<br> Objects.requireNonNull(value);<br> return new Optional<>(value);<br> }<br> }<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>This is a final class, so obviously it can not be matched with a record pattern, but we can have methods that see an instance of Optional as records.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>By example, we can have a method "asPresent" that returns a record with one component containing the value if the value inside the Optional is present. I'm using the prefix "as" here because this is the one commonly used in Java (it also the same semantics as as the keyword "as" in C#/Kotlin).<br></div><div><br data-mce-bogus="1"></div><div>The return value of the method asPresent(), is a record, here named "$CarrierPresent".<br data-mce-bogus="1"></div><div>So we get something like this at declaration site:<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div> public /*value*/ record $CarrierPresent<T>(T value) {}<br><br> public $CarrierPresent<T> asPresent() {<br> if (value == null) {<br> return null;<br> }<br> return new $CarrierPresent<>(value);<br> }<br><br data-mce-bogus="1"></div><div>and at call site, we can use "when" + instanceof inside a switch like this:<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div> var optional = ..<br> var result = switch (optional) {<br> case Optional<String> opt when opt.asPresent() instanceof Optional.$CarrierPresent<String>(String s) -> ...<br> ...<br> };<br><br data-mce-bogus="1"></div><div>The same way, we also way a method "asEmpty()" defined like this:<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div> public /*value*/ record $CarrierEmpty() {}<br><br> public $CarrierEmpty asEmpty() {<br> if (value != null) {<br> return null;<br> }<br> return new $CarrierEmpty();<br> }<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>so we can switch on both asPresent() and asEmpty() like this:<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div> var optional = ..<br> var result = switch (optional) {<br> case Optional<String> opt when opt.asPresent() instanceof Optional.$CarrierPresent<String>(String s) -> ...<br> case Optional<String> opt when opt.asEmpty() instanceof Optional.$CarrierEmpty() -> ...</div><div> default -> throw new MatchException("boom !", null);<br> };<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>So we are able to write the code using actual Java so adding a pattern that calls a method can be seen as an exercise of adding syntactic sugar. Brian, yes, i'm well aware of the shortcoming of that approach but the idea here is to try to meet you in the middle.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>At use site, we want to simplify the code to write something like<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div> var optional = ...<br> var result = switch (optional) {<br> case Optional<String>.asPresent(String s) -> ...<br> case Optional<String>.asEmpty() -> ...<br> ...<br> };<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>We can note that the '.' just after the type (here Optional<String>) does not work exactly as a method call, in the example, this is *not* a static method call, it's more a reference to a method that appears to be an instance method in our case. As Brian said, like the semantics of '::', we can expect to reference an instance method, a static method or a bound method. </div><div><br data-mce-bogus="1"></div><div>If we imagine a method static "asInteger" declared in String, that works like parseInt but return null instead of throwing an exeception, and a method asInteger() on a Matcher that call String.asInteger() on matcher.group(1)<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div> instance method: OptionalInt.asPresent(int v) -> ...<br data-mce-bogus="1"></div><div> static method. String.asInteger(int v) -> ...<br data-mce-bogus="1"></div><div> bound method. matcher.asInteger(int v) -> ...<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>Like '::', I think we should method call without a prefix because it is not clear if the prefix should be the instance switched upon or the current "this" in that case. So as a user if you want to bound this, you will have to write it explicitly.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>The method referenced by a method pattern can be an abstract method, an instance method, a static method, a default method, a varargs, etc. What is important is that this method must return either a record or something that can be deconstructed as a record.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>At declaration site, we have to decide two things, how to represent a record without having users to declare a record and how to represent match/no-match, i.e. is returning null (or any other signal for no match) should be something hidden or not. I see no reason to couple those two points, so those can be seen as two different features.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>How to declare a carrier on the spot ?<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>Technically, it does not have to be a record, it has to be something that describes all its component at runtime, let's call it a carrier.</div><div>So it should be something like this for asPresent and asEmpty<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div> public carrier (T value) asPresent() { ... }<br data-mce-bogus="1"></div><div> public carrier () asEmpty() { ... }<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>We can note that carrier can be a keyword on the method itself or a keyword on the return type of the method or even a way to define a type, e.g. carrier(int a, int b) correspond to the type of an instance of the product of int a and int b. <br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>Another question is that if carrier if a way to create a carrier type, can it be used in other place than just as a return type.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>How to create a carrier ?<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>For asPresent(), we want to be able to express the that a carrier can either represent several values or no match.</div><div>This can be done, either using a pair of keyword like match/nomatch<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div> public carrier(T value) asPresent() {<br> if (value == null) {<br> return nomatch;<br> }<br> return match (value);<br> }<br data-mce-bogus="1"></div><div><br></div><div>or using null and reusing the keyword carrier after "new" so the syntax looks like an instantiation of a normal class.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div> public carrier(T value) asPresent() {<br> if (value == null) {<br> return null;<br> }<br> return new carrier (value);<br> }<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>With that in mind, we can introduce a second example, destructuring Map.Entry.<br data-mce-bogus="1"></div><div>So if we add a method "asEntry", actually, we can write it like this<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div> public interface Map<K,V> {<br> public interface Entry<K,V> {</div><div> public /*value*/ record $Carrier<K, V>(K key, V value) {}<br> abstract $Carrier/*!*/<K, V> asEntry();<br> }<br><br> public static <K,V> Map.Entry<K,V> entry(K key, V value) { ... }<br> }<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>Using a carrier, Entry can be written like this:</div><div><div> public interface Entry<K,V> {</div><div> abstract carrier!(K key, V value) asEntry();<br> }</div><br data-mce-bogus="1"></div><div>You can notice, that if we introduce '!' and '?' in the future, it can be used to indicate that the match is total,</div><div>and perhaps it means that the return null should be explicit and not implicit.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>and pattern match like this:<br data-mce-bogus="1"></div><div> var entry = ...<br> var result = switch (entry) {<br> case Map.Entry<String,Integer>.asEntry(String key, Integer value) -> ...<br> };<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>Unlikle in Java, in the example above, Map.Entry is defined as a functional interface, so because asEntry() is a method, a lambda can be used like this:<br></div><div><br data-mce-bogus="1"></div><div> Map<String, String> entry = () -> new carrier("foo", "bar");<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>The last example is the class Point and how to specify a deconstructor.<br data-mce-bogus="1"></div><div>Again, actually we can write:<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div> class Point {<br> private int x, y;</div><div><br> public Point(int x, int y) { this.x = x; this.y = y; }<br><br> public /*value*/ record $Carrier(int x, int y) {}<br><br> public static $Carrier/*!*/ deconstructor(Point that) {<br> Objects.requireNonNull(that);<br> return new $Carrier(that.x, that.y);<br> }<br> }<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>Note that here, we want the method to be static so it can not be overriden by the subclasses.</div><div><br data-mce-bogus="1"></div><div>or using the carrier syntax:</div><div><div> public static carrier(int x, int y) deconstructor(Point that) {<br> Objects.requireNonNull(that);<br> return new carrier(that.x, that.y);<br> }<br></div><div><br data-mce-bogus="1"></div><div>Here, we can make "deconstructor" a local keyword. The compiler will in that case verifies that it returns a carrier and that it can not return null.<br data-mce-bogus="1"></div><br data-mce-bogus="1"></div><div>Another interresting things with the carrier notation is that is make the syntax for destructuring of an object quite obvious:</div><div> var point = ...<br data-mce-bogus="1"></div><div> carrier(int x, int y) = point;<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>I think that by not starting from the deconstructor, the notion of inverse methods make less sense.</div><div>I think that the notion of carrier / carrier type is less disruptive that the notion of member patterns.<br></div><div><br data-mce-bogus="1"></div><div>regards,<br data-mce-bogus="1"></div><div>Rémi<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><hr id="zwchr" data-marker="__DIVIDER__"><div data-marker="__HEADERS__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><b>From: </b>"Brian Goetz" <brian.goetz@oracle.com><br><b>To: </b>"amber-spec-experts" <amber-spec-experts@openjdk.java.net><br><b>Sent: </b>Friday, March 29, 2024 10:58:54 PM<br><b>Subject: </b>Member Patterns -- the bikeshed<br></blockquote></div><div data-marker="__QUOTED_TEXT__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><font size="4" face="monospace">We now come to the long-awaited
bikeshed discussion on what member patterns should look like. <br>
<br>
Bikeshed disclaimer for EG: <br>
- This is likely to evoke strong opinions, so please take pains
to be especially constructive<br>
- Long reply-to-reply threads should be avoided even more than
usual<br>
- Holistic, considered replies preferred<br>
- Please change subject line if commenting on a sub-topic or
tangential<br>
concern<br>
<br>
Special reminders for Remi:<br>
- Use of words like "should", "must", "shouldn't", "mistake",
"wrong", "broken"<br>
are strictly forbidden. <br>
- If in doubt, ask questions first. <br>
<br>
Notes for external observers:<br>
- This is a working document for the EG; the discussion may
continue for a<br>
while before there is an official proposal. Please be patient.<br>
<br>
<br>
# Pattern declaration: the bikeshed<br>
<br>
We've largely identified the model for what kinds of patterns we
need to<br>
express, but there are still several degrees of freedom in the
syntax.<br>
<br>
As the model has simplified during the design process, the space
of syntax<br>
choices has been pruned back, which is a good thing. However,
there are still<br>
quite a few smaller decisions to be made. Not all of the
considerations are<br>
orthogonal, so while they are presented individually, this is not
a "pick one<br>
from each column" menu. <br>
<br>
Some of these simplifications include:<br>
<br>
- Patterns with "input arguments" have been removed; another way
to get to what<br>
this gave us may come back in another form. <br>
- I have grown increasingly skeptical of the value of the
imperative `match`<br>
statement. With better totality analysis, I think it can be
eliminated.<br>
<br>
We can discuss these separately but I would like to sync first on
the broad<br>
strokes for how patterns are expressed.<br>
<br>
## Object model requirements<br>
<br>
As outlined in "Towards Member Patterns", the basic model is that
patterns are<br>
the dual of other executable members (constructors, static
methods, instance<br>
methods.) While they are like methods in that they have inputs,
outputs, names,<br>
and an imperative body, they have additional degrees of freedom
that<br>
constructors and methods lack: <br>
<br>
- Patterns are, in general, _conditional_ (they can succeed or
fail), and only<br>
produce bindings (outputs) when they succeed. This
conditionality is<br>
understood by the language's flow analysis, and is used for
computing scoping<br>
and definite assignment.<br>
- Methods can return at most one value; when a pattern completes
successfully,<br>
it may bind multiple values.<br>
- All patterns have a _match candidate_, which is a
distinguished,<br>
possibly-implicit parameter. Some patterns also have a
receiver, which is<br>
also a distinguished, possibly-implicit parameter. In some
such cases the<br>
receiver and match candidate are aliased, but in others these
may refer to<br>
different objects.<br>
<br>
So a pattern is a named executable member that takes a _match
candidate_ as a<br>
possibly-implicit parameter, maybe takes a receiver as an implicit
parameter,<br>
and has zero or more conditional _bindings_. Its body can perform
imperative<br>
computation, and can terminate either with match failure or
success. In the<br>
success case, it must provide a value for each binding.<br>
<br>
Deconstruction patterns are special in many of the same ways
constructors are:<br>
they are constrained in their name, inheritance, and probably
their<br>
conditionality (they should probably always succeed). Just as the
syntax for<br>
constructors differs slightly from that of instance methods, the
syntax for<br>
deconstructors may differ slightly from that of instance
patterns. Static<br>
patterns, like static methods, have no receiver and do not have
access to the<br>
type parameters of the enclosing class. <br>
<br>
Like constructors and methods, patterns can be overloaded, but in
accordance<br>
with their duality to constructors and methods, the overloading
happens on the<br>
_bindings_, not the inputs. <br>
<br>
## Use-site syntax<br>
<br>
There are several kinds of type-driven patterns built into the
language: type<br>
patterns and record patterns. A type pattern in a `switch` looks
like:<br>
<br>
case String s: ...<br>
<br>
And a record pattern looks like:<br>
<br>
case MyRecord(P1, P2, ...): ...<br>
<br>
where `P1..Pn` are nested patterns that are recursively matched to
the<br>
components of the record. This use-site syntax for record
patterns was chosen<br>
for its similarity to the construction syntax, to highlight that a
record<br>
pattern is the dual of record construction. <br>
<br>
**Deconstruction patterns.** The simplest kind of member pattern,
a<br>
deconstruction pattern, will have the same use-site syntax as a
record pattern;<br>
record patterns can be thought of as a deconstruction pattern
"acquired for<br>
free" by records, just as records do with constructors, accessors,
object<br>
methods, etc. So the use of a deconstruction pattern for `Point`
looks like:<br>
<br>
case Point(var x, var y): ...<br>
<br>
whether `Point` is a record or an ordinary class equipped with a
suitable<br>
deconstruction pattern. <br>
<br>
**Static patterns.** Continuing with the idea that the
destructuring syntax<br>
should evoke the aggregation syntax, there is an obvious candidate
for the<br>
use-site syntax for static patterns: <br>
<br>
case Optional.of(var e): ...<br>
case Optional.empty(): ...<br>
<br>
**Instance patterns.** Uses of instance patterns will likely come
in two forms,<br>
analogous to bound and unbound instance method references,
depending on whether<br>
the receiver and the match candidate are the same object. In the
unbound form,<br>
used when the receiver is the same object as the match candidate,
the pattern<br>
name is qualified by a _type_:<br>
<br>
```<br>
Class<?> k = ...<br>
switch (k) { <br>
// Qualified by type<br>
case Class.arrayClass(var componentType): ...<br>
}<br>
```<br>
<br>
This means that we _resolve_ the pattern `arrayClass` starting at
`Class` and<br>
_select_ the pattern using the receiver, `k`. We may also be able
to omit the<br>
class qualifier if the static type of the match candidate is
sufficient to<br>
resolve the desired pattern.<br>
<br>
In the bound form, used when the receiver is distinct from the
match candidate,<br>
the pattern name is qualified with an explicit _receiver
expression_. As an<br>
example, consider an interface that captures primitive widening
and narrowing<br>
conversions, such as those between `int` and `long`. In the
widening direction,<br>
conversion is unconditional, so this can be modeled as a method
from `int` to<br>
`long`. In the other direction, conversion is conditional, so
this is better<br>
modeled as a _pattern_ whose match candidate is `long` and which
binds an `int`<br>
on success. Since these are instance methods of some class (say,<br>
`NumericConversion<T,U>`), we need to provide the receiver
instance in order to<br>
resolve the pattern:<br>
<br>
```<br>
NumericConversion<int, long> nc = ...<br>
<br>
switch (aLong) { <br>
case nc.narrowed(int i): <br>
...<br>
}<br>
```<br>
<br>
The explicit receiver syntax would also be used if we exposed
regular expression<br>
matching as a pattern on the `j.u.r.Pattern` object (the name
collision on<br>
`Pattern` is unfortunate). Imagine we added a `matching` instance
pattern to<br>
`j.u.r.Pattern`; then we could use it in `instanceof` as follows:
<br>
<br>
```<br>
static final java.util.regex.Pattern P =
Pattern.compile("(a*)(b*)"); <br>
...<br>
if (aString instanceof P.matching(String as, String bs)) { ... }<br>
```<br>
<br>
Each of these use-site syntaxes is modeled after the use-site
syntax for a<br>
method invocation or method reference.<br>
<br>
## Declaration-site syntax<br>
<br>
To avoid being biased by the simpler cases, we're going to work
all the cases<br>
concurrently rather than starting with the simpler cases and
working up. (It<br>
might seem sensible to start with deconstructors, since they are
the "easy"<br>
case, but if we did that, we would likely be biased by their
simplicity and then<br>
find ourselves painted into a corner.) As our example gallery, we
will consider:<br>
<br>
- Deconstruction pattern for `Point`;<br>
- Static patterns for `Optional::of` and `Optional::empty`;<br>
- Static pattern for "power of two" (illustrating a computations
where success<br>
or failure, and computation of bindings, cannot easily be
separated);<br>
- Instance pattern for `Class::arrayClass` (used unbound);<br>
- Instance pattern for `Pattern::matching` on regular expressions
(used bound).<br>
<br>
Member patterns, like methods, have _names_. (We can think of
constructors as<br>
being named for their enclosing classes, and the same for
deconstructors.) All<br>
member patterns have a (possibly empty) ordered list of
_bindings_, which are<br>
the dual of constructor or method parameters. Bindings, in turn,
have names and<br>
types. And like constructors and methods, member patterns have a
_body_ which<br>
is a block statement. Member patterns also have a _match
candidate_, which is a<br>
likely-implicit method parameter. <br>
<br>
### Member patterns as inverse methods and constructors<br>
<br>
Regardless of syntax, let us remind ourselves that that
deconstructors are the<br>
categorical dual to constructors (coconstructors), and pattern
methods are the<br>
categorical dual to methods (comethods). They are dual in their
structure: a<br>
constructor or method takes N arguments and produces a result, the
corresponding<br>
member pattern consumes a match candidate and (conditionally)
produces N<br>
bindings. <br>
<br>
Moreover, they are semantically dual: the return value produced by
construction<br>
or factory invocation is the match candidate for the corresponding
member<br>
pattern, and the bindings produced by a member pattern are the
answers to the<br>
_Pattern Question_ -- "could this object have come from an
invocation of my<br>
dual, and if so, with what arguments." <br>
<br>
### What do we call them?<br>
<br>
Given the significant overlap between methods and patterns, the
first question<br>
about the declaration we need to settle is how to identify a
member pattern<br>
declaration as distinct from a method or constructor declaration.
_Towards<br>
Member Patterns_ tried out a syntax that recognized these as
_inverse_ methods<br>
and constructors:<br>
<br>
public Point(int x, int y) { ... }<br>
public inverse Point(int x, int y) { ... }<br>
<br>
While this is a principled choice which clearly highlights the
duality, and one<br>
that might be good for specification and verbal description, it is
questionable<br>
whether this would be a great syntax for reading and writing
programs. <br>
<br>
A more traditional option is to choose a "noun" (conditional)
keyword, such as<br>
`pattern`, `matcher`, `extractor`, `view`, etc:<br>
<br>
public pattern Point(int x, int y) { ... }<br>
<br>
If we are using a noun keyword to identify pattern declarations,
we could use<br>
the same noun for all of them, or we could choose a different one
for<br>
deconstruction patterns:<br>
<br>
public deconstructor Point(int x, int y) { ... }<br>
<br>
Alternately, we could reach for a symbol to indicate that we are
talking about<br>
an inverted member. C++ fans might suggest<br>
<br>
public ~Point(int x, int y) { ... }<br>
<br>
but this is too cryptic (it's evocative once you see it, but then
it becomes<br>
less evocative as we move away from deconstructors towards
instance patterns.)<br>
<br>
If we wish to offer finer-grained control over conditionality, we
might<br>
additionally need a `total` / `partial` modifier, though I would
prefer to avoid<br>
that.<br>
<br>
Of the keyword candidates, there is one that stands out (for good
and bad)<br>
because it connects to something that is already in the language:
`pattern`. On<br>
the one hand, using the term `pattern` for the declaration is a
slight abuse; on<br>
the other, users will immediately connect it with "ah, so that's
how I make a<br>
new pattern" or "so that's what happens when I match against this
pattern."<br>
(Lisps would resolve this tension by calling it `defpattern`.)<br>
<br>
The others (`matcher`, `view`, `extractor`, etc) are all made-up
terms that<br>
don't connect to anything else in the language, for better or
worse. If we pick<br>
one of these, we are asking users to sort out _three_ separate new
things in<br>
their heads: (use-site) patterns, (declaration-site) matchers, and
the rules of<br>
how patterns and matchers are connected. Calling them both
"patterns", despite<br>
the mild abuse of terminology, ties them together in a way that
recognizes their<br>
connection.<br>
<br>
My personal position: `pattern` is the strongest candidate here,
despite some<br>
flaws.<br>
<br>
### Binding lists and match candidates<br>
<br>
There are two obvious alternatives for describing the binding list
and match<br>
candidate of a pattern declaration, both with their roots in the
constructor and<br>
method syntax: <br>
<br>
- Pretend that a pattern declaration is like a method with
multiple return, and<br>
put the binding list in the "return position", and make the
match candidate<br>
an ordinary parameter;<br>
- Lean into the inverse relationship between constructors and
methods (and<br>
consistency with the use-site syntax), and put the binding list
in the<br>
"parameter list position". For static patterns and some
instance patterns,<br>
which need to explicitly identify the match candidate type,
there are several<br>
sub-options:<br>
- Lean further into the duality, putting the match candidate
type in the<br>
"return position";<br>
- Put the match candidate type somewhere else, where it is less
likely to be<br>
confused for a method return.<br>
<br>
The "method-like" approach might look like this:<br>
<br>
```<br>
class Point { <br>
// Constructor and deconstructor<br>
public Point(int x, int y) { ... }<br>
public pattern (int x, int y) Point(Point target) { ... }<br>
...<br>
}<br>
<br>
class Optional<T> { <br>
// Static factory and pattern<br>
public static<T> Optional<T> of(T t) { ... }<br>
public static<T> pattern (T t) of(Optional<T>
target) { ... }<br>
...<br>
}<br>
```<br>
<br>
The "inverse" approach might look like:<br>
<br>
```<br>
class Point { <br>
// Constructor and deconstructor<br>
public Point(int x, int y) { ... }<br>
public pattern Point(int x, int y) { ... }<br>
...<br>
}<br>
<br>
class Optional<T> { <br>
// Static factory and pattern (using the first sub-option)<br>
public static<T> Optional<T> of(T t) { ... }<br>
public static<T> pattern Optional<T> of(T t) { ...
}<br>
...<br>
}<br>
```<br>
<br>
With the "method-like" approach, the match candidate gets an
explicit name<br>
selected by the author; with the inverse approach, we can go with
a predefined<br>
name such as `that`. (Because deconstructors do not have
receivers, we could by<br>
abuse of notation arrange for the keyword `this` to refer instead
to the match<br>
candidate within the body of a deconstructor. While this might
seem to lead to<br>
a more familiar notation for writing deconstructors, it would
create a<br>
gratuitous asymmetry between the bodies of deconstruction patterns
and those of<br>
other patterns.)<br>
<br>
Between these choices, nearly all the considerations favor the
"inverse"<br>
approach:<br>
<br>
- The "inverse" approach makes the declaration look like the use
site. This<br>
highlights that `pattern Point(int x, int y)` is what gets
invoked when you<br>
match against the pattern use `Point(int x, int y)`. (This
point is so<br>
strong that we should probably just stop here.)<br>
- The "inverse" members also look like their duals; the only
difference is the<br>
`pattern` keyword (and possibly the placement of the match
candidate type).<br>
This makes matched pairs much more obvious, and such matched
pairs will be<br>
critical both for future language features and for library
idioms.<br>
- The method-like approach is suggestive of multiple return or
tuples, which is<br>
probably helpful for the first few minutes but actually harmful
in the long<br>
term. This feature is _not_ (much as some people would like to
believe) about<br>
multiple return or tuples, and playing into this misperception
will only make<br>
it harder to truly understand. So this suggestion ends up
propping up the<br>
wrong mental model. <br>
<br>
The main downside of the "inverse" approach is the one-time speed
bump of the<br>
unfamiliarity of the inverted syntax. (The "method-like" syntax
also has its<br>
own speed bumps, it is just unfamiliar in different ways.) But
unlike the<br>
advantages of the inverse approach, which continue to add value
forever, this<br>
speed bump is a one-time hurdle to get over. <br>
<br>
To smooth out the speed bumps of the inverse approach, we can
consider moving<br>
the position of the match candidate for static and (suitable)
instance pattern<br>
declarations, such as:<br>
<br>
```<br>
class Optional<T> { <br>
// the usual static factory<br>
public static<T> Optional<T> of(T t) { ... }<br>
<br>
// Various ways of writing the corresponding pattern<br>
public static<T> pattern of(T t) for Optional<T> {
... }<br>
// or ...<br>
public static<T> pattern(Optional<T>) of(T t) {
... }<br>
// or ...<br>
public static<T> pattern(Optional<T> that) of(T t)
{ ... }<br>
// or ...<br>
public static<T> pattern<Optional<T>> of(T
t) { ... }<br>
...<br>
}<br>
```<br>
<br>
(The deconstructor example looks the same with either variant.)
Of these,<br>
treating the match candidate like a "parameter" of "pattern" is
probably the<br>
most evocative:<br>
<br>
```<br>
public static<T> pattern(Optional<T> that) of(T t) {
... }<br>
```<br>
<br>
as it can be read as "pattern taking the parameter
`Optional<T> that` called<br>
`of`, binding `T`, and is a short departure from the inverse
syntax.<br>
<br>
The main value of the various rearrangements is that users don't
need to think<br>
about things operating in reverse to parse the syntax. This
trades some of the<br>
secondary point (patterns looking almost exactly like their
inverses) for a<br>
certain amount of cognitive load, while maintaining the most
important<br>
consideration: that the declaration site look like the use site. <br>
<br>
For instance pattern declarations, if the match candidate type is
the same as<br>
the receiver type, the match candidate type can be elided as it is
with<br>
deconstructors. <br>
<br>
My personal position: the "multiple return" version is terrible;
all the<br>
sub-variants of the inverse version are probably workable.<br>
<br>
### Naming the match candidate<br>
<br>
We've been assuming so far that the match candidate always has a
fixed name,<br>
such as `that`; this is an entirely workable approach. Some of
the variants are<br>
also amenable to allowing authors to explicitly select a name for
the match<br>
candidate. For example, if we put the match candidate as a
"parameter" to the `pattern` keyword, there is an obvious place to
put the name:<br>
<br>
```<br>
static<T> pattern(Optional<T> target) of(T t) { ... }<br>
```<br>
<br>
My personal opinion: I don't think this degree of freedom buys us
much, and in<br>
the long run readability probably benefits by picking a fixed name
like `that`<br>
and sticking with it. Even with a fixed name, if there is a
sensible position<br>
for the name, allowing users to type `that` for explicitness is
fine (as we do<br>
with instance methods, though many people don't know this.) We
may even want to<br>
require it.<br>
<br>
## Body types<br>
<br>
Just as there are two obvious approaches for the declaration,
there are two<br>
obvious approaches we could take for the body (though there is
some coupling<br>
between them.) We'll call the two body approaches _imperative_
and<br>
_functional_. <br>
<br>
The imperative approach treats bindings as initially-DU variables
that must be<br>
DA on successful completion, getting their value through ordinary
assignment;<br>
the functional approach sets all the bindings at once,
positionally. Either<br>
way, member patterns (except maybe deconstructors) also need a way
to<br>
differentiate a successful match from a failed match. <br>
<br>
Here is the `Point` deconstructor with both imperative and
functional style. The<br>
functional style uses a placeholder `match` statement to indicate
a successful<br>
match and provision of bindings:<br>
<br>
```<br>
class Point {<br>
int x, y;<br>
<br>
Point(int x, int y) {<br>
this.x = x;<br>
this.y = y;<br>
}<br>
<br>
// Imperative style, deconstructor always succeeds<br>
pattern Point(int x, int y) {<br>
x = that.x;<br>
y = that.y;<br>
}<br>
<br>
// Functional style<br>
pattern Point(int x, int y) {<br>
match(that.x, that.y);<br>
}<br>
}<br>
```<br>
<br>
There are some obvious differences here. In the imperative style,
the dtor body<br>
looks much more like the reverse of the ctor body. The functional
style is more<br>
concise (and amenable to further concision via the "concise method
bodies"<br>
mechanism in the future), as well as a number of less obvious
differences. For<br>
deconstructors, the imperative approach is likely to feel more
natural because<br>
of the obvious symmetry with constructors.<br>
<br>
In reality, it is _premature at this point to have an opinion_,
because we<br>
haven't yet seen the full scope of the problem; deconstructors are
a special<br>
case in many ways, which almost surely is distorting our initial
opinion. As we<br>
move towards conditional patterns (and pattern lambdas), our
opinions may flip.<br>
<br>
Regardless of which we pick, there are some additional syntactic
choices to be<br>
made -- what syntax to use to indicate success (we used `match` in
the above<br>
example) or failure. (We should be especially careful around
trying to reuse<br>
words like `return`, `break`, or `yield` because, in the case
where there are<br>
zero bindings (which is allowable), it becomes unclear whether
they mean "fail"<br>
or "succeed with zero bindings".) <br>
<br>
### Success and failure<br>
<br>
Except for possibly deconstructors, which we may require to be
total, a pattern<br>
declaration needs a way to indicate success and failure. In the
examples above,<br>
we posited a `match` statement to indicate success in the
functional approach,<br>
and in both examples leaned on the "implicit success" of
deconstructors (under<br>
the assumption they always succeed). Now let's look at the more
general case to<br>
figure out what else is needed.<br>
<br>
For a static pattern like `Optional::of`, success is conditional.
Using<br>
`match-fail` as a placeholder for "the match failed", this might
look like<br>
(functional version):<br>
<br>
```<br>
public static<T> pattern(Optional<T> that) of(T t) { <br>
if (that.isPresent())<br>
match (that.get());<br>
else<br>
match-fail;<br>
}<br>
```<br>
<br>
The imperative version is less pretty, though. Using
`match-success` as a<br>
placeholder:<br>
<br>
```<br>
public static<T> pattern(Optional<T> that) of(T t) { <br>
if (that.isPresent()) {<br>
t = that.get();<br>
match-success;<br>
}<br>
else<br>
match-fail;<br>
}<br>
```<br>
<br>
Both arms of the `if` feel excessively ceremonial here. And if we
chose to not<br>
make all deconstruction patterns unconditional, deconstructors
would likely need<br>
some explicit success as well:<br>
<br>
```<br>
pattern Point(int x, int y) {<br>
x = that.x;<br>
y = that.y;<br>
match-success;<br>
}<br>
```<br>
<br>
It might be tempting to try and eliminate the need for explicit
success by<br>
inferring it from whether or not the bindings are DA or not, but
this is<br>
error-prone, is less type-checkable, and falls apart completely
for patterns<br>
with no bindings.<br>
<br>
### Implicit failure in the functional approach<br>
<br>
One of the ceremonial-seeming aspects of `Optional::of` above is
having to say<br>
`else match-fail`, which doesn't feel like it adds a lot of
value. Perhaps we<br>
can be more concise without losing clarity. <br>
<br>
Most conditional patterns will have a predicate to determine
matching, and then<br>
some conditional code to compute the bindings and claim success.
Having to say<br>
"and if the predicate didn't hold, then I fail" seems like
ceremony for the<br>
author and noise for the reader. Instead, if a conditional
pattern falls off<br>
the end without matching, we could treat that as simply not
matching:<br>
<br>
```<br>
public static<T> pattern(Optional<T> that) of(T t) { <br>
if (that.isPresent())<br>
match (that.get());<br>
}<br>
```<br>
<br>
This says what we mean: if the optional is present, then this
pattern succeeds<br>
and bind the contents of the `Optional`. As long as our "succeed"
construct<br>
strongly enough connotes that we are terminating abruptly and
successfully, this<br>
code is perfectly clear. And most conditional patterns will look
a lot like<br>
`Optional::of`; do some sort of test and if it succeeds, extract
the state and<br>
bind it.<br>
<br>
At first glance, this "implicit fail" idiom may seem error-prone
or sloppy. But<br>
after writing a few dozen patterns, one quickly tires of saying
"else<br>
match-fail" -- and the reader doesn't necessarily appreciate
reading it either. <br>
<br>
Implicit failure also simplifies the selection of how we
explicitly indicate<br>
failure; using `return` in a pattern for "no match" becomes pretty
much a forced<br>
move. We observe that (in a void method), "return" and "falling
off the end"<br>
are equivalent; if "falling off the end" means "no match", then so
should an<br>
explicit `return`. So in those few cases where we need to
explicitly signal "no<br>
match", we can just use `return`. It won't come up that often,
but here's an<br>
example where it does: <br>
<br>
```<br>
static pattern(int that) powerOfTwo(int exp) {<br>
int exp = 0;<br>
<br>
if (that < 1)<br>
return; // explicit fail<br>
<br>
while (that > 1) {<br>
if (that % 2 == 0) {<br>
that /= 2;<br>
++exp;<br>
}<br>
else<br>
return; // explicit fail<br>
}<br>
match (exp);<br>
}<br>
```<br>
<br>
As a bonus, if `return` as match failure is a forced move, we need
only select a<br>
term for "successful match" (which obviously can't be `return`).
We could use<br>
`match` as we have in the examples, or a variant like `matched` or
`matches`.<br>
But rather than just creating a new control operator, we have an
opportunity to<br>
lean into the duality a little harder, by including the pattern
syntax in the<br>
match:<br>
<br>
```<br>
matches of(that.get());<br>
```<br>
<br>
or the (optionally?) qualified (inferring type arguments, as we do
at the use<br>
site):<br>
<br>
```<br>
matches Optional.of(that.get());<br>
```<br>
<br>
These "use the name" approaches trades a small amount of verbosity
to gain a<br>
higher degree of fidelity to the pattern use site (and to evoke
the comethod<br>
completion.) <br>
<br>
If we don't choose "implicit fail", we would have to invent _two_
new control<br>
flow statements to indicate "success" and "failure". <br>
<br>
My personal position: for the functional approach, implicit
failure both makes<br>
the code simpler and clearer, and after you get used to it, you
don't want to go<br>
back. Whether we say `match` or `matches` or `matches
<pattern-name>` are all<br>
workable, though I like some variant that names the pattern.<br>
<br>
### Implicit success in the imperative approach<br>
<br>
In the imperative approach, we can be implicit as well, but it
feels more<br>
natural (at least, initially) to choose implicit success rather
than failure.<br>
This works great for unconditional patterns:<br>
<br>
```<br>
pattern Point(int x, int y) {<br>
x = that.x;<br>
y = that.y;<br>
// implicit success<br>
}<br>
```<br>
<br>
but not quite as well for conditional patterns:<br>
<br>
```<br>
static<T> pattern(Optional<T> that) of(T t) { <br>
if (that.isPresent()) {<br>
t = that.get();<br>
}<br>
else<br>
match-fail;<br>
// implicit success<br>
}<br>
```<br>
<br>
We can eliminate one of the arms of the if, with the more concise
(but<br>
convoluted) inversion:<br>
<br>
```<br>
static<T> pattern(Optional<T> that) of(T t) { <br>
if (!that.isPresent()) <br>
match-fail;<br>
t = that.get();<br>
// implicit success<br>
}<br>
```<br>
<br>
Just as with the functional approach, if we choose imperative and
"implicit<br>
success", using `return` to indicate success is pretty much a
forced move. <br>
<br>
### Imperative is a trap<br>
<br>
If we assume that functional implies implicit failure, and
imperative implies<br>
implicit success, then our choices become: <br>
<br>
```<br>
class Optional<T> { <br>
public static<T> Optional<T> of(T t) { ... }<br>
<br>
// imperative, implicit success<br>
public static<T> pattern(Optional<T> that) of(T t)
{ <br>
if (that.isPresent()) {<br>
t = that.get();<br>
}<br>
else<br>
match-fail;<br>
}<br>
<br>
// functional, implicit failure<br>
public static<T> pattern(Optional<T> that) of(T t)
{ <br>
if (that.isPresent())<br>
matches of(that.get());<br>
}<br>
}<br>
```<br>
<br>
Once we get past deconstructors, the imperative approach looks
worse by<br>
comparison because we need to assign all the bindings (which is
_O(n)_<br>
assignments) _and also_ indicate success or failure somehow,
whereas in the<br>
functional style all can be done together with a single `matches`
statement.<br>
<br>
Looking at the alternatives, except maybe for unconditional
patterns, the<br>
functional example above seems a lot more natural. The imperative
approach<br>
works with deconstructors (assuming they are not conditional), but
does not<br>
scale so well to conditionality -- which is the essence of
patterns.<br>
<br>
From a theoretical perspective, the method-comethod duality also
gives us a<br>
forceful nudge towards the functional approach. In a method, the
method<br>
arguments are specified as a positional list of expressions at the
use site: <br>
<br>
m(a, b, c)<br>
<br>
and these values are invisibly copied into the parameter slots of
the method<br>
prior to frame activation. The dual to that for a comethod to
similarly convey<br>
the bindings in a positional list of expressions (as they must
either all be<br>
produced or none), where they are copied into the slots provided
at the use<br>
site, as is indicated by `matches` in the above examples. <br>
<br>
My personal position: the imperative style feels like a trap. It
seems<br>
"obvious" at first if we start with deconstructors, but becomes
increasingly<br>
difficult when we get past this case, and gets in the way of other<br>
opportunities. The last gasp before acceptance is the discomfort
that dtor and<br>
ctor bodies are written in different styles, but in the rear-view
mirror, this<br>
feels like a non-issue. <br>
<br>
### Derive imperative from functional?<br>
<br>
If we start with "functional with implicit failure", we can
possibly rescue<br>
imperative by deriving a version of imperative from functional, by
"overloading"<br>
the match-success operator. <br>
<br>
If we have a pattern whose binding names are `b1..bn` of types
`B1..Bn`, then<br>
the `matches` operator must take a list of expressions `e1..en`
whose arity and<br>
types are compatible with `B1..Bn`. But we could allow `matches`
to also have a<br>
nilary form, which would have the effect of being shorthand for <br>
<br>
matches <pattern-name>(b1, b2, ..., bn)<br>
<br>
where each of `b1..bn` must be DA at the point of matching. This
means that we<br>
could express patterns in either form:<br>
<br>
```<br>
class Optional<T> { <br>
public static<T> Optional<T> of(T t) { ... }<br>
<br>
// imperative, derived from functional with implicit failure<br>
public static<T> pattern(Optional<T> that) of(T t)
{ <br>
if (that.isPresent()) {<br>
t = that.get();<br>
matches of;<br>
}<br>
}<br>
<br>
public static<T> pattern(Optional<T> that) of(T t)
{ <br>
if (that.isPresent())<br>
matches of(that.get());<br>
}<br>
}<br>
```<br>
<br>
This flexibility allows users to select a more verbose expression
in exchange<br>
for a clearer association of expressions and bindings, though as
we'll see, it<br>
does come with some additional constraints.<br>
<br>
### Wrapping an existing API<br>
<br>
Nearly every library has methods (sometimes sets of methods) that
are patterns<br>
in disguise, such as the pair of methods `isArray` and
`getComponentType` in<br>
`Class`, or the `Matcher` helper type in `java.util.regex`.
Library maintainers<br>
will likely want to wrap (or replace) these with real patterns, so
these can<br>
participate more effectively in conditional contexts, and in some
cases,<br>
highlight their duality with factory methods.<br>
<br>
Matching a string against a `j.u.r.Pattern` regular expression has
all the same<br>
elements as a pattern, just with an ad-hoc API (and one that I
have to look up<br>
every time). But we can fairly easily wrap a true pattern around
the existing<br>
API. To match against a `Pattern` today, we pass the match
candidate to<br>
`Pattern::matcher`, which returns a `Matcher` with accessors
`Matcher::matches`<br>
(did it match) and `Matcher::group` (conditionally extract a
particular capture<br>
group.) If we want to wrap this with a pattern called
`regexMatch`:<br>
<br>
```<br>
pattern(String that) regexMatch(String... groups) {<br>
Matcher m = this.matcher(that);<br>
if (m.matches())<br>
matches Pattern.regexMatch(IntStream.range(1,
m.groupCount())<br>
.map(Matcher::group)<br>
.toArray(String[]::new));<br>
// whole lotta matchin' goin' on<br>
}<br>
```<br>
<br>
This says that a `j.u.r.Pattern` has an instance pattern called
`regex`, whose<br>
match candidate is `String`, and which binds a varargs of `String`
corresponding<br>
to the capture groups. The implementation simply delegates to the
existing<br>
`j.u.r.Matcher` API. This means that `j.u.r.Pattern` becomes a
sort of "pattern<br>
object", and we can use it as a receiver at the use site: <br>
<br>
```<br>
static Pattern As = Pattern.compile("(a*)");<br>
static Pattern Bs = Pattern.compile("(b*)");<br>
...<br>
switch (string) { <br>
case As.regexMatch(var as): ...<br>
case Bs.regexMatch(var bs): ...<br>
...<br>
}<br>
```<br>
<br>
### Odds and ends<br>
<br>
There are a number of loose ends here. We could choose other
names for the<br>
match-success and match-fail operations, including trying to reuse
`break` or<br>
`yield`. But, this reuse is tricky; it must be very clear whether
a given form<br>
of abrupt completion means "success" or "failure", because in the
case of<br>
patterns with no bindings, we will have no other syntactic cues to
help<br>
disambiguate. (I think having a single `matches`, with implicit
failure and<br>
`return` meaning failure, is the sweet spot here.)<br>
<br>
Another question is whether the binding list introduces
corresponding variables<br>
into the scope of the body. For imperative, the answer is "surely
yes"; for<br>
functional, the answer is "maybe" (unless we want to do the trick
where we<br>
derive imperative from functional, in which case the answer is
"yes" again.)<br>
<br>
If the binding list does not correspond to variables in the body,
this may be<br>
initially discomforting; because they do not declare program
elements, they may<br>
feel that they are left "dangling". But even if they are not
declaring<br>
_program_ elements, they are still declaring _API_ elements
(similar to the<br>
return type of a method.) We will want to provide Javadoc on the
bindings, just<br>
like with parameters; we will want to match up binding names in
deconstructors<br>
with parameter names in constructors; we may even someday want to
support<br>
by-name binding at the use site (e.g., `case Foo(a: var a)`). The
names are<br>
needed for all of these, just not for the body. Names still
matter. My take<br>
here is that this is a transient "different is scary" reaction,
one that we<br>
would get over quickly.<br>
<br>
A final question is whether we should consider unqualified names
as implicitly<br>
qualified by `that` (and also `this`, for instance patterns, with
some conflict<br>
resolution). Users will probably grow tired of typing `that.` all
the time, and most of the time, the unqualified use is perfectly
readable.<br>
<br>
## Exhaustiveness <br>
<br>
There is one last syntax question in front of us: how to indicate
that a set of<br>
patterns are (claimed to be) exhaustive on a given match candidate
type. We see<br>
this with `Optional::of` and `Optional::empty`; it would be sad if
the compiler<br>
did not realize that these two patterns together were exhaustive
on `Optional`.<br>
This is not a feature that will be used often, but not having it
at all will be<br>
a repeated irritant.<br>
<br>
The best I've come up with is to call these `case` patterns, where
a set of<br>
`case` patterns for a given match candidate type in a given class
are asserted<br>
to be an exhaustive set:<br>
<br>
```<br>
class Optional<T> { <br>
static<T> Optional<T> of(T t) { ... }<br>
static<T> Optional<T> empty() { ... }<br>
<br>
static<T> case pattern of(T t) for Optional<T> {
... }<br>
static<T> case pattern empty() for Optional<T> {
... }<br>
}<br>
```<br>
<br>
Because they may not be truly exhaustive, `switch` constructs will
have to back<br>
up the static assumption of exhaustiveness with a dynamic check,
as we do for<br>
other sets of exhaustive patterns that may have remainder.<br>
<br>
I've experimented with variants of `sealed` but it felt more
forced, so this is<br>
the best I've come up with.<br>
<br>
## Example: patterns delegating to other patterns<br>
<br>
Pattern implementations must compose. Just as a subclass
constructor delegates<br>
to a superclass constructor, the same should be true for
deconstructors.<br>
Here's a typical superclass-subclass pair: <br>
<br>
```<br>
class A { <br>
private final int a;<br>
<br>
public A(int a) { this.a = a; }<br>
public pattern A(int a) { matches A(that.a); }<br>
}<br>
<br>
class B extends A { <br>
private final int b;<br>
<br>
public B(int a, int b) { <br>
super(a);<br>
this.b = b; <br>
}<br>
<br>
// Imperative style <br>
public pattern B(int a, int b) {<br>
if (that instanceof super(var aa)) {<br>
a = aa;<br>
b = that.b;<br>
matches B;<br>
}<br>
}<br>
<br>
// Functional style<br>
public pattern B(int a, int b) {<br>
if (that instanceof super(var a)) <br>
matches B(a, b);<br>
}<br>
}<br>
```<br>
<br>
(Ignore the flow analysis and totality for the time being; we'll
come back to<br>
this in a separate document.)<br>
<br>
The first thing that jumps out at us is that, in the imperative
version, we had<br>
to create a "garbage" variable `aa` to receive the binding,
because `a` was<br>
already in scope, and then we have to copy the garbage variable
into the real<br>
binding variable. Users will surely balk at this, and rightly so.
In the<br>
functional version (depending on the choices from "Odds and Ends")
we are free<br>
to use the more natural name and avoid the roundabout locution.<br>
<br>
We might be tempted to fix the "garbage variable" problem by
inventing another<br>
sub-feature: the ability to use an existing variable as the target
of a binding,<br>
such as:<br>
<br>
```<br>
pattern Point(int a, int b) {<br>
if (this instanceof A(__bind a))<br>
b = this.b;<br>
}<br>
```<br>
<br>
But, I think the language is stronger without this feature, for
two reasons.<br>
First, having to reason about whether a pattern match introduces a
new binding<br>
or assigns to an existing variables is additional cognitive load
for users to<br>
reason about, and second, having assignment to locals happening
through<br>
something other than assignment introduces additional complexity
in finding<br>
where a variable is modified. While we can argue about the
general utility of<br>
this feature, bringing it in just to solve the garbage-variable
problem is<br>
particularly unattractive. <br>
<br>
## Pattern lambdas<br>
<br>
One final consideration is is that patterns may also have a lambda
form. Given<br>
a single-abstract-pattern (SAP) interface:<br>
<br>
```<br>
interface Converter<T,U> { <br>
pattern(T t) convert(U u);<br>
}<br>
```<br>
<br>
one can implement such a pattern with a lambda. Such a lambda has
one parameter<br>
(the match candidate), and its body looks like the body of a
declared pattern:<br>
<br>
```<br>
Converter<Integer, Short> c = <br>
i -> { <br>
if (i >= Short.MIN_VALUE && i <=
Short.MAX_VALUE)<br>
matches Converter.convert((short) i);<br>
};<br>
```<br>
<br>
Because the bindings of the pattern lambda are defined in the
interface, not in<br>
the lambda, this is one more reason not to like the imperative
version: it is<br>
brittle, and alpha-renaming bindings in the interface would be a<br>
source-incompatible change.<br>
<br>
## Example gallery<br>
<br>
Here's all the pattern examples so far, and a few more, using the
suggested<br>
style (functional, implicit fail, implicit `that`-qualification):<br>
<br>
```<br>
// Point dtor <br>
pattern Point(int x, int y) {<br>
matches Point(x, y);<br>
}<br>
<br>
// Optional -- static patterns for Optional::of, Optional::empty<br>
static<T> case pattern(Optional<T> that) of(T t) { <br>
if (isPresent())<br>
matches of(t);<br>
}<br>
<br>
static<T> case pattern(Optional<T> that) empty() { <br>
if (!isPresent())<br>
matches empty();<br>
}<br>
<br>
// Class -- instance pattern for arrayClass (match candidate type
inferred)<br>
pattern arrayClass(Class<?> componentType) { <br>
if (that.isArray())<br>
matches arrayClass(that.getComponentType());<br>
}<br>
<br>
// regular expression -- instance pattern in j.u.r.Pattern<br>
pattern(String that) regexMatch(String... groups) {<br>
Matcher m = matcher(that);<br>
if (m.matches())<br>
matches Pattern.regexMatch(IntStream.range(1,
m.groupCount())<br>
.map(Matcher::group)<br>
.toArray(String[]::new));<br>
}<br>
<br>
// power of two (somewhere)<br>
static pattern(int that) powerOfTwo(int exp) {<br>
int exp = 0;<br>
<br>
if (that < 1)<br>
return;<br>
<br>
while (that > 1) {<br>
if (that % 2 == 0) {<br>
that /= 2;<br>
exp++;<br>
}<br>
else<br>
return;<br>
}<br>
matches powerOfTwo(exp);<br>
}<br>
```<br>
<br>
## Closing thoughts<br>
<br>
I came out of this exploration with very different conclusions
than I expected<br>
when going in. At first, the "inverse" syntax seemed stilted, but
over time it<br>
started to seem more obvious. Similarly, I went in expecting to
prefer the<br>
imperative approach for the body, but over time, started to warm
to the<br>
functional approach, and eventually concluded it was basically a
forced move if<br>
we want to support more than just deconstructors. And I started
out skeptical<br>
of "implicit fail", but after writing a few dozen patterns with
it, going back<br>
to fully explicit felt painful. All of this is to say, you should
hold your<br>
initial opinions at arm's length, and give the alternatives a
chance to sink in.<br>
<br>
For most _conditional_ patterns (and conditionality is at the
heart of pattern<br>
matching), the functional approach cleanly highlights both the
match predicate<br>
and the flow of values, and is considerably less fussy than the
imperative<br>
approach in the same situation; `Optional::of`,
`Class::arrayClass`, and `regex`<br>
look great here, much better than the would with imperative. None
of these<br>
illustrate delegation, but in the presence of delegation, the gap
gets even<br>
wider.<br>
<br></font><br></blockquote></div></div></body></html>