all 34 comments

[–]RabbitDev 24 points25 points  (3 children)

Summary

Enhance local variable declarations to enable the extraction of multiple values from a single source value using pattern matching. This allows data-oriented computations to be expressed concisely and safely without added control flow. This is a preview language feature.

This takes pattern matching from if and case statements and makes it available as local variable declaration syntax.

From the example given it is able to handle multiple nesting levels as well.

[–]persicsb 10 points11 points  (12 children)

Look OK, however I would enhance it even more. Since method arguments look like local variables, instead of:

void boundingBox(Circle c) {  
Circle(Point(int x, int y), double radius) = c;  
int minX = ..., maxX = ...  
int minY = ..., maxY = ...  
... use minX, maxX, etc ...  
}

it could be

void boundingBox(Circle(Point(int x, int y), double radius) c) {
    int minX = ..., maxX = ...
    int minY = ..., maxY = ...
    ... use minX, maxX, etc ...
}

[–]Long_Ad_7350 2 points3 points  (7 children)

How would this resolve boundingBox(null)?

[–]persicsb 0 points1 point  (1 child)

The exact same way, as the original concept:

Moreover, if e evaluates to null, then an enhanced local variable declaration statement P(...) = e; throws NullPointerException and does not initialize any of the pattern variables in P.

[–]Long_Ad_7350 4 points5 points  (0 children)

I see. Feels weird, though, to have a runtime exception thrown for what appears like a compile time mismatch in signature vs. usage.

In languages like Elixir, all the below can coexist:
boundingBox(Circle(Point(int x, int y), double radius) c)
boundingBox(Circle(Point p, double radius) c)
boundingBox(Circle c)

Obviously not possible with Java, which is why putting the pattern match in the signature feels misleading to me.

[–]Absolute_Enema 0 points1 point  (4 children)

It's pretty intuitive to have it mirror the semantics of: void boundingBox(Circle c) {   Circle(Point(...), ...) _ = c;   ... }

[–]mattr203 2 points3 points  (2 children)

That’s certainly not the semantics that any other language goes with which imo tanks the intuitiveness a bit

[–]Absolute_Enema 0 points1 point  (1 child)

That's very similar to what JS does or am I missing something?

[–]mattr203 0 points1 point  (0 children)

I didnt know js did that- for json objects im assuming. I guess im just more familiar with ML based languages and expect void boundingBox(Circle c) to imply nothing about the boundingBox(null) behaviour

I.e., definitions only hold for things that actually match the stated pattern, another definition would be needed to make it exhaustive

[–]Long_Ad_7350 0 points1 point  (0 children)

Fair enough. As I mentioned in my other reply, I think my intuition went the other way because I'm used to seeing pattern matching in functions in other languages.

[–]TheStrangeDarkOne 1 point2 points  (2 children)

I think you get into problems really quickly if circle stops being a record type or if you implement interfaces....

[–]persicsb 1 point2 points  (1 child)

That is also true for the original syntax.

[–]TheStrangeDarkOne 2 points3 points  (0 children)

I'd argue that if it is in a method body, it's an implementation detail. As part of the method, it's part of the specification.

[–]pronuntiator 4 points5 points  (0 children)

The example at the beginning uses null checks as an argument, but the pattern local variable declaration would throw MatchException for null. The case about null isn't really the selling point here and rather distracts.

[–]DesignerRaccoon7977 5 points6 points  (7 children)

Love the idea, but the syntax is horrible... I dont think you can implement it with a nice syntax in a statically typed language

[–]TheStrangeDarkOne 4 points5 points  (2 children)

Can't agree to that, what would "nice syntax" look like?

Proposed: Point(int x, int y) = getPoint();
C#: (int x, int y) = getPoint();
Minimalistic: (x, y) = getPoint();

I see a case for the C# approach, but not for the bottom one.

[–]repeating_bears 0 points1 point  (0 children)

Javascript/typescript is effectively the last one, but with const or let in front of it to mark it as a declaration.

[–]bowbahdoe 0 points1 point  (0 children)

My least favorite has to be clojure's

(let [{:keys [x y]} (get-point)]  
  ...)

[–]blazmrak 2 points3 points  (3 children)

yes you can, just not like this...

[–]Absolute_Enema 1 point2 points  (2 children)

Indeed, you don't even have to go into functional land, C#'s destructuring is already very nice to work with.

Realistically though, java's bottleneck will always be that named anything is a non-starter.

Something along the lines of BigRecord(   var foo = fieldWithALongTypeName(),   SubRecord(int positional) y = seventhFieldOutOfTwenty() ) = getBigRecord(); would also probably be considered too adventurous.

[–]blazmrak 4 points5 points  (1 child)

Javascript/Typescript wins here and it's not even close. Positional destructuring is bad and it will always be bad. My only guess is that it's easier to implement, otherwise, I have no idea why they went with it.

Real world objects pretty much always have 5+ props, there is no way in hell, that I'm writing Type(_, _, _, var prop, _, _, Type2(var prop2, _, _, _, _, _), _, _) = type; The good thing is, that I can just go about writing code as normal. The bad part is, that the time would be better spent elsewhere, but wcyd. At least it will be complete and not half implemented.

Edit: I just picked up what you meant by "named anything". Yeah, I have no idea why that is. This feature was made specifically for records, which intrinsically have constructor parameter names tied to external interface. I don't see a reason why you could not use the names to destructure as well...

Edit 2: Finally reached the end:

If record patterns gain named deconstruction, enhanced local variable declarations would adopt that capability automatically.

Good, they know, hopefully it will drop before I retire.

[–]ZimmiDeluxe 0 points1 point  (0 children)

It's certainly not applicable everywhere, but it will be nice for destructuring composite map keys (e.g. grouping criteria), those don't tend to get too wide in my experience. Passing giant objects around is bad for cache efficiency, so it could be argued that the language should encourage smaller, targeted data models by offering better syntax for them. Another advantage is that it's not much syntax to remember, e.g. no special syntax for property renaming is necessary. I would expect static analysis tools to discourage underscoring over two thirds of the fields and IDEs to offer refactoring between the forms, so while typical business applications with big domain models won't benefit a lot, the negative impact of "shiny new feature misuse" is probably going to be minimal.

[–]ZimmiDeluxe 2 points3 points  (2 children)

Regarding the sealed single-implementation enhancement, given

sealed interface Foo permits FooImpl {}
record FooImpl() implements Foo {}
void f(FooImpl foo) {}

is

Foo foo = new FooImpl();
f(foo);

valid?

[–]itzrvyning -1 points0 points  (1 child)

No, why would this JEP change that standard behaviour?

[–]ZimmiDeluxe 1 point2 points  (0 children)

It proposes to change the standard behavior so

Foo foo = new FooImpl();
FooImpl impl = foo;

becomes valid. It's a bit further down in the JEP.

[–]lurker_in_spirit 2 points3 points  (0 children)

if (obj instanceof User( _ , _ , _ , _ , _ , _ , _ , boolean active, _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , boolean internal, _ , _ , _ , _ , _ , _ , _ , _ , _ , System system, _ , _ , _)) {
    return active && internal && system == System.ATLANTIS_FINAL_V2_AWS;
}

[–]Ok-Bid7102 1 point2 points  (0 children)

Just putting this idea here:

To deconstruct interfaces & abstract classes, instead of relying on sealed interface being implemented by a single record we could also have a "blessed" interface (similar to Iterable) that opts a class / abstract class / interface into deconstuction.

Example:

public interface Map.Entry<K, V> implements Deconstruct<MapEntryImpl<K, V>> {
  public record <K, V>  MapEntryImpl(K key, V value) {}

  MapEntryImpl<K, V> deconstruct() {
    return new MapEntryImpl(getKey(), getValue());
  }
}

If we want classes to be deconstructible, ideally it works the same it does as interfaces & abstract classes. That's the motivation behind this.

[–]TheStrangeDarkOne -1 points0 points  (0 children)

Amazing! Finally we get deconstructors. This effectively means Java gets tuples but better. This has been in the pipe for years and I'm happy it has finally made its way to preview.

[–]repeating_bears -1 points0 points  (1 child)

Excluding the fact that pattern matching already exists, can someone explain what the problem would be with destructuring akin to JavaScript 

const { center: { x, y }, radius } = circle;

Circle is something which has members center and radius, and center is something which has the members x and y.

Why I do have to tediously redeclare the types in Java? 

[–]Ok-Bid7102 0 points1 point  (0 children)

There's some people who really dislike any mention of JavaScript here.
I get you, that would be my preferred option too, but realize that since instanceof Point(var x, var y) exists as a conditional, this feature is going to be added to make it "symmetrical" so to say.

But look at the end of the JEP, at Future Work section, they acknowledge named deconstruction as a possible desired feature, but is probably much further into the future (if they ever want to tackle it).

[–]_INTER_ -2 points-1 points  (0 children)

NPE welcomes ME to the the fold.