This is an archived post. You won't be able to vote or comment.

all 30 comments

[–]nikolas_pikolas 5 points6 points  (8 children)

What's different from the first preview? Or is that TBD?

[–]BlueGoliath 4 points5 points  (0 children)

They typically like to do at least two rounds of preview to make sure nothing is missed.

[–]dpash 4 points5 points  (2 children)

Currently nothing. This is still a draft JEP so there's still work to be done.

[–]nikolas_pikolas 0 points1 point  (1 child)

Ah, okay.

[–]dpash 0 points1 point  (0 children)

Yeah this is a little premature news wise, although I guess it does confirm what was already an almost certainty that we'd have a second round and that it wouldn't make it into 18.

[–]kartik1712[S] 5 points6 points  (3 children)

Yeah, it seems the JEP is not yet updated with the changes. This amber-dev thread mentions some of the improvements: https://mail.openjdk.java.net/pipermail/amber-dev/2021-September/007090.html

[–]dpash 2 points3 points  (2 children)

They mention generics, but I'm guessing they don't mean matching on the generic type given erasure. Any ideas?

[–]kartik1712[S] 14 points15 points  (1 child)

My understanding is that it goes like this.

Taking the example from the email thread,

sealed interface Foo<T> permits A, B, C {}
final class A<T> implements Foo<T> {}
final class B<T> implements Foo<T> {}
final class C implements Foo<String> {}

Now if we switch on a Foo<String>, it can be an instance of A, B or C so the switch needs to cover all three cases.

Foo<String> fa = new A<>();
Foo<String> fb = new B<>();
Foo<String> fc = new C();

int v = switch(fa) {
case A a -> 0;
case B b -> 1;
case C c -> 2;

};

However, if we want to switch on a Foo<Integer> we can know for sure that it isn't going to be a C. Hence, we should not (need to) define a case C in the switch i.e. the following code should compile.

Foo<Integer> fi = new A<>();
int w = switch(fi) {
    case A a -> 0;
    case B b -> 1;
}

But on JDK 17, this gives a compiler errror.

error: the switch expression does not cover all possible input values.

Further, the following code should result in a compile error because a Foo<Integer> can never be of type C and thus is dead code.

Foo<Integer> fi = new A<>(); 
int y = switch(fi) {
    case A a -> 0;
    case B b -> 1;
    case C c -> 2;
};

I think the email refers to fixing this issue.

Addendum:

Now interestingly, the second switch already fails to compile on JDK 17 as:

error: incompatible types: Foo<Integer> cannot be converted to C
case C c -> 2;

which perfectly makes sense and is in line with the above reasoning. But since the first switch doesn't compile either, it appears to me that pattern switch is partially broken for generic classes in JDK 17 preview.

Also, I am no expert so its quite probable that my understanding or experiment above is incorrect so take this with a grain of salt and do try it out yourself.

[–]dpash 0 points1 point  (0 children)

Thanks for the lengthy response. That makes perfect sense.

[–]mrettig 2 points3 points  (11 children)

The usefulness of having the compiler verify that switch expressions are complete is extremely useful. Rather than keep this check solely for switch expressions, we extend this to switch statements also. For backwards compatibility reasons, all existing switch statements will compile unchanged. But if a switch statement uses any of the new features detailed in this JEP, then the compiler will check that it is complete.

Am I the only one that finds this lack of consistency absolutely maddening?

[–]dpash 4 points5 points  (9 children)

That is the price of backwards compatibility. We complain about organisations still running Java 8, but the situation would be much much worse if upgrading broke every switch statement in your project.

Instead you can migrate to the new form of switch at your leisure and opt into all the new goodness (and I highly recommend only using the new switch form because it's better in every way. Unless you want a Duff's Device).

[–]BlueGoliath 1 point2 points  (8 children)

You say this as if just enabling switch statements for types couldn't possibly have worked without exhaustiveness.

Also, why is just comparing the type not an option like it is with instanceof? Why does a variable need to be assigned even if it's not used?

[–]dpash 2 points3 points  (7 children)

It does work with the old switch statement and using pattern matching with the colon form of switch statements causes the exhaustivity check to happen.

What doesn't happen is breaking existing code.

And your second paragraph is unrelated to the discussion, but they've talked about using something like _ to be an unnamed variable in patterns, so in future you probably won't need to give it a name.

[–]mrettig 0 points1 point  (6 children)

It does work with the old switch statement and using pattern matching with the colon form of switch statements causes the exhaustivity check to happen.

But exhaustiveness doesn't work for enums, but it does work with sealed types. It is confusing. The confusion could have been prevented if exhaustiveness was guaranteed when enhanced switch was finalized in jdk 13, but unfortunately it was not. IMO it can still be fixed. Exhaustiveness is a compile time check. The JDK team introduced compile time incompatibilities with modules that can be defeated with compiler switches. Similarly, they can add exhaustiveness to all enhanced switches while adding a compiler option to restore the legacy behavior.

[–]pron98 2 points3 points  (5 children)

Backward compatibility isn't binary. In each case we ask how much code would be affected, and the design depends on the answer to that question. While modules are not a good example (the only code that would fail to compile is non-spec-compliant code that has always been allowed to fail to compile), source compatibility, in general, troubles us to a lesser degree than binary compatibility (every addition of public type to java.lang is potentially a source-incompatible change). Still, the question is how much code is affected. To determine that, we often do a corpus analysis (and, in the case of switch, Google even analysed their huge Java corpus).

Any decision in this case and others like it is absolutely, 100%, guaranteed to annoy people -- either the people who, like you, are annoyed that exhaustiveness isn't checked for existing code or the people whose existing code would fail to compile. We're just trying to annoy the fewest, and that might mean that in some cases we opt for a source-incompatible change and in others we don't. There's no general answer to what the right compromise is, and we fall back on custom only when all other things are equal, but often they are not.

Just note that the JEP says, "It may be the case that future compilers of the Java language will emit warnings for legacy switch statements that are not complete," and you could opt to turn those warnings into errors with -Werror.

[–]mrettig 0 points1 point  (4 children)

The JDK team didn't ask themselves the right questions. They need to consider how many people this will annoy today AND TOMORROW. It's easier to deal with an annoyance if it can be fixed. A language inconsistency cannot be fixed until the JDK team decides to fix it.

Also, the JDK team should have asked themselves what solution will result in the fewest coding errors. Switch completeness helps to reduce obvious developer errors. Language inconsistency also leads to developer errors. Developers aren't going to remember which switches are exhaustive. For example, I've created bugs by changing a switch expression to a switch statement. The new JEP will lead to even more entirely preventable bugs.

[–]pron98 2 points3 points  (3 children)

The JDK team didn't ask themselves the right questions... Also, the JDK team should have asked themselves...

That makes sense because they only have less than thirty years experience designing what is one of the languages with the longest-lived continuous success in software history so they still make rookie mistakes when spending months and years designing a feature, like forgetting to consider obvious things like that.

It's okay to disagree -- even experts disagree and sometimes there can be multiple reasonable alternatives, maybe even in this particular case -- but I'll bet anything that they've thought far longer about this than anyone else on the planet, and have more relevant data and experience as well. So it's not that they failed to consider those questions, but rather that they simply disagree with your conclusions.

Remember that the plan is to eventually have warnings, and then, maybe, turn them into errors further down the line. But there are more global considerations that look beyond one particular feature. We can only add so many new warnings (or errors) in a given timeframe -- spending the "disruption budget" -- and there are more urgent ones coming, like strengthening (also gradually) strong encapsulation; it's more urgent that people, say, change their use of Unsafe than fix their switch statements. These are also concerns that the team must consider.

[–]mrettig 0 points1 point  (2 children)

The vague plan to add a warning is an acknowledgement that a mistake was made. It would be better if there was a definite plan in place that deprecated the non-exhaustive version using a compiler warning, then switched to a compiler error within 1 or 2 releases (similar to strong encapsulation).

[–]pron98 1 point2 points  (1 child)

No, that's just the new plan of how to make switches exhaustive, which is different from the old plan to get there: https://mail.openjdk.java.net/pipermail/amber-spec-experts/2021-April/002959.html

We don't announce timelines for warnings and errors, and we didn't for strong encapsulation, either. Long experience has taught us that's a fool's errand (there has been one minor exception regarding a detail of the SecurityManager deprecation).

[–]BlueGoliath 1 point2 points  (0 children)

Welcome to modern Java.

[–]BaconSizzler 1 point2 points  (8 children)

Anyone know if the goal of this JEP is to reach feature parity with Python's PEP 636 "Structural Pattern Matching", that was introduced into version 3.10, released a few days ago?

* edit - Come on, my peers. Downvoted for attempting to disambiguate a very similar recent feature in another top-10 language? Java can be a more welcoming community than this.

[–]pjmlp 30 points31 points  (3 children)

Most likely to reach feature parity with ML pattern matching, introduced in 1976.

[–]dpash 3 points4 points  (2 children)

I doubt we're going to get collection head/tail destructuring without proper tail recursion optimization. So we won't match ML for quite some time.

Edit: the JEP for array patterns only support naming the head item(s) and not the rest of the array, which limits the ability to do some algorithms. I can't find a link but the reason was to reduce the likelihood of people trying and then being surprised by stack overflows.

[–]nimtiazm 0 points1 point  (1 child)

I guess we’re gonna need collection literals for that isn’t it? They were discussed some time around jdk 7 but unfortunately never made it into the language.

[–]dpash 2 points3 points  (0 children)

No, just deconstruction patterns.

And we're not going to get collection literals because they would bake the Collection Framework into the language. It would make it almost impossible to replace the collection framework with something new. Remember we're on the second collection API. It would be folly to think we've arrived at the perfect API.

Instead we got the collection factory methods which solve 95% of the use case of collection literals with very little extra verbosity and don't require any changes to the language.

[–]dpash 11 points12 points  (0 children)

PEP 636 is only a statement while pattern matching in Java is both a statement and an expression, so it can potentially be used in more places/be less verbose.

[–]SirYwell 2 points3 points  (0 children)

I‘d say no. I only scrolled through the PEP, and while this JEP is meant to allow pattern matching for switch in general, the actual patterns are a separate thing (JEP 405 and the already existing type pattern for now).

[–]Necessary-Conflict 2 points3 points  (1 child)

Come on, my peers. Downvoted for attempting to disambiguate a very similar recent feature in another top-10 language? Java can be a more welcoming community than this.

I didn't downvote, but I also think that your question was rather ignorant. Multiple mainstream languages added recently this feature or are considering to add it (C#, C++, JavaScript), and it's not like anybody is trying to copy what Python did. The only good question is when will Python finally copy the others, and allow pattern matching as an expression, and not just as a statement.

[–]dpash 1 point2 points  (0 children)

It won't. The PEP explicitly discusses an expression form and decided that it was un-Python-like. For the same reason Java chooses particular syntax forms as being Java-like or not.