Pattern assignment should work in all of the following contexts:
- Assignment statements: P = e
- foreach-loops: for (P : e) { ... }
- (optional) try-with-resources: try (P = e) { ... }
- (optional) method formals: void m(Point(var x, var y) p) { ... }
- (optional) lambda formals: (Point(var x, var y) p) -> { ... }
It is easy (and normal) here, I think, to be a little confused. At the
beginning I was thinking "boy inference of lambdas with target typing
AND patterns is gonna make javac cry" - but I think the key to a lot
of these is that whenever you see a pattern declaration, it's like if
you had an _explicit_ type. So, while there might be magic at runtime
to decompose the value into the binding variables, from a static
perspective, typing the lambda:
Point(var x, var y) -> distance(x, y)
is no different than typing this:
Point p -> distance(p.x, p.y)
Exactly; think of it as a syntactic desugaring
Point(var x, var y) p -> e
to
Point p -> { Point(var x, var y) = p; return e; }
My intent was actually to require the top-level variable (p) in
lambda/method formals, which emphasizes this.
Crucially, the pattern provides an explicit type (including generic
type arguments, at least initially).
From here I guess the next step would be, for a lambda like this:
Box<String>(String s) -> ...
to avoid the outer `<String>` - but I'm not sure about that step. I
think if we treat a pattern as a replacement for an explicit type,
things works nicely and there's a simple user model to explain. If we
make it too magic, it could be more concise, but also more confusing.
Agree.
I was thinking that maybe another way to get at that is by using
unchecked exceptions - e.g. if pattern failure raised a well-known
unchecked exception, then users could have a chance (if they want) at
looking as to why things failed.
try {
P p = e;
...
} catch (...)
The problem with this though is that the handler code is very distant
from where the failure has happened (unlike in let/else).
Not only that, but if the exception is unchecked, it is really not
obvious that the match is even partial. I like that we require an
intrinsically conditional control flow construct (instanceof, switch,
catch) for partial patterns, and only allow total patterns in things
that "look total".
And we can't really do:
P p;
try {
p = e;
} catch (...)
Because the proposed pattern assignment doesn't support some form of
blank declaration - e.g. a way to say:
Point(int x, int y);
if (...) {
// assign pattern from here
} else {
// assign pattern from here
}
Is this something we view as a limitation?
It's a glass which is either P% full or (100-P)% empty :) But I would
much rather drive towards making what you wrote legal, in some way,
rather than make assignment partial in a less-than-fully-transparent way.
We've already encountered another place where we might want
bind-to-existing: composition of deconstructors/patterns. Suppose:
class A {
int a;
deconstructor(int a) { a = this.a; }
}
class B extends A {
int b;
deconstructor(int a, int b) {
* super(a);*
b = this.b;
}
}
If we can't invoke another pattern with bind-to-one-of-my-bindings, then
we'd have to write something like:
deconstructor(int a, int b) {
super(var aa) = this;
a = aa;
b = this.b;
}
While this is not the worst thing in the world, it will surely be a
persistent irritation. So *some* way to say "bind to this variable"
possibly under some restrictions (DU?) seems desirable. If we had that,
then your if-else would do the trick:
int x, y; // blank locals, therefore DU
if (!(target instanceof Point(__my x, __my y)) {
x = y = 0;
}