16 ms·
Java record pattern matching in JDK 19
- hn_throwaway_99 4y agoCool to see Java getting these features. After having been a Java dev most of my life, and then moving to Typescript years ago, it's hard to believe I went so long without this. Related, Typescript is the only language I know of that through flow analysis does not require you to redefine the variable. E.g. you can do something like if (typeof foo === 'string') { //Typescript knows foo is a string here } All the other languages I know of, like this proposal for Java, require the variable to be redefined. I personally find the Typescript way, as VSCode will give you appropriate type warnings in the right places, easier to use in practice. Just curious if there are any other languages that do it like TS.
- jung_j 4y agoJava 14 does have something similar https://openjdk.java.net/jeps/305 https://openjdk.java.net/jeps/305
- hn_throwaway_99 4y agoNo, Java requires you to redefine the variable, i.e. if (obj instanceof String s) { // s is a String here, but obj is not }
- Pet_Ant 4y agoThat I would say still counts as “similar” not “the same”
- SemanticStrengh 4y agobut is it a deep copy??
- sebazzz 4y agoIt is not a copy. It still references the same object.
- danachow 4y agoThat’s the same, just that the scoped definition is implicit.
- hn_throwaway_99 4y agoNo, that's the entire point of my question. In Typescript (and Kotlin, as others have noted), the type system knows within the scope of that if statement that obj is a string, so it lets you call string-specific methods on obj withOUT introducing the new variable s.
- danachow 4y agoWhat I'm saying is that this: if (typeof obj === 'string') { // obj is a string } is really not anything meaningfully different than this if (obj is string as obj) { // as in C# // obj is a string (shadowing the outer scoped obj) } or this if (obj instanceof String obj) { // obj is a String here } you can imagine a pseudo TypeScript language like this if (typeof obj === 'string' as obj) { // obj is a string } Actual TypeScript merely allows you to elide the "as obj" and all it needs to trigger this is the "typeof obj === <string literal>" inside of an if expression. This can be done by a simple syntactic replacement, it doesn't require control flow analysis to get this specific feature. But yes, if you have a more general computed expression, that would apply, but that was not the case you were stating. Ie the variable isn't the issue (redefining in this case seems like a distinction without a difference)... However this does work in TS, which is a demonstration of CFA: const isStr = typeof obj === "string"; if (isStr) { // obj is str } For what it's worth I think CFA is useful in TypeScript based on it at its core being a structural typed bandaid over JS, but I think these specific CFA type narrowing features are redundant in stronger typed languages.
- pjmlp 4y agoJava cannot do the same without breaking backwards compatibility, hence the idea to introduce a variable explicitly.
- Smaug123 4y agoF# also has: let foo : obj = failwith "" match foo with | :? SomeType as blah -> // use blah : SomeType (You're allowed to just use the identifier `foo` in the match arm.)
- halfmatthalfcat 4y agoAs does Scala: match thing { case a: String => // use a as string case a @ MyCaseClass(b: String, c, d) => // use a as the instance of MyCaseClass or use b as a string }
- Squarex 4y agoKotlin is the same as typescript.
- swaranga 4y agoThere are some edge cases because of which this had to be done, I think. For example calling static methods via the instance variables, the actual method called would be the static type of the variable at compile time and not the actual type at runtime: public static void main(String[] args) { Parent instance = new Child(); if (instance instanceof Child p) { instance.print(); // prints Parent p.print(); // prints Child } } static class Parent { static void print() { System.out.println("Parent"); } } static class Child extends Parent { static void print() { System.out.println("Child"); } } Hence, with flow typing, existing code could break in subtle ways.
- SemanticStrengh 4y agoHow does Kotlin solve those use cases? > instance.print(); // prints Parent why? the parent print should be shadowed/overidden by the child bruh
- spullara 4y agostatics don't override
- SemanticStrengh 4y agooops my bad
- yarg 4y agoDoes java provide warnings against calling static methods on Objects?
- smarks 4y agoYes, if javac is given the `-Xlint:static` option.
- hilbertseries 4y agoIf you’re writing code calling static methods on instances of classes, you deserve to have that code broken.
- papercrane 4y agoThe Typescript way would only work for local variables in Java. Since another thread might update a field between the instanceof check and the use of the field.
- SemanticStrengh 4y agothose cases are rare. Defensive copying should be optin, not the default. Java really deep copy objects for even the most basic instanceof checks??
- kaba0 4y agoYou can’t change the object’s type in Java, so if you hold on to the same reference it will be safe to use after the instanceof check.
- zarzavat 4y agoYou’re definitely holding it wrong if you have polymorphic fields in your lock-free shared memory. Or at least holding it weirdly.
- gizmo686 4y agoThats the problem with language design. If the language allows something, it needs to correctly support it; even if actually doing it is a bad idea.
- peterkelly 4y agoThis is an issue in TypeScript even with single-threaded code; it (always/sometimes?) assumes function calls won't affect the results of type narrowing tests: https://github.com/microsoft/TypeScript/issues/9998 https://github.com/microsoft/TypeScript/issues/9998
- pkulak 4y agoKotlin does that as well, and it's a JVM language.
- SemanticStrengh 4y agoDon't be mistaken, Typescript do not has proper pattern matching support, The proposal is far from being adopted https://github.com/tc39/proposal-pattern-matching https://github.com/tc39/proposal-pattern-matching > Related, Typescript is the only language I know of that through flow analysis does not require you to redefine the variable. E.g. you can do something like This is called smart casting and is widely used in Kotlin
- viewfromafar 4y agoThe PL crowd calls that type system feature "occurrence typing."
- SemanticStrengh 4y agoyes or flow typing https://en.wikipedia.org/wiki/Flow-sensitive_typing https://en.wikipedia.org/wiki/Flow-sensitive_typing It probably overlap with the research on gradual typing too
- PartiallyTyped 4y agoMypy (Python) does this as well. VSCode and PyCharm provide typehints after if isinstance(foo, str): # code
- dehrmann 4y agoSometimes. If foo isn't local scope, this won't work because it could be modified in another thread.
- cwalv 4y agoI used to find the workaround pretty jarring: lfoo = nonlocal_foo if isinstance(lfoo, ...): Since the checker is correct in these cases, I accepted the ugly and got used to it pretty quickly
- skybrian 4y agoDart also does this.
- markdog12 4y agoAnd it's getting patterns too: https://github.com/dart-lang/language/tree/master/working/0546-patterns https://github.com/dart-lang/language/tree/master/working/05...
- danachow 4y agoThat’s not dataflow analysis. It looks like a cast similar to something like in C# ie: if (foo is string as foo) where the “as foo” scoped variable is implicit. It’s basically implicitly redefining the variable with limited scope. Type guards are a slick feature but it is just an expression match and some syntactic sugar. Even user defined type guards in typescript have to be explicitly declared as such. This would be more an example of CFA. const a = “string”; if (typeof v === a)
- hn_throwaway_99 4y agoIt's not just a simple cast, but I should have used the correct term which is "control flow analysis". It's not just redefining the variable, control flow analysis works in lots of different places besides just typeof checks, and is possible in many cases because TS is structurally typed, not nominally typed.
- TheMatten 4y agoAnd of course, Haskell has equivalent too: case eqT @a @String of Just Refl -> ...
- moomin 4y agoRefinement typing (the TS way) is relatively common in type systems that sit on top of dynamic languages because basically they have no choice. Binding to a new variable, however, is a heck of a lot easier to analyse so tends to be done in languages that don’t need to implement refinement typing. A couple of interesting exceptions: explicit interfaces make it impractical in C#, and there’s a project called liquid Haskell that adds refinement typing on top of an already strong type system.
- dwaite 4y agoSome of this is a consequence of how typescript works - it carries any structural constraints it can find forward in an advisory capacity, because it is somewhat decoupled from the quite-dynamic runtime behavior of the javascript engine. It isn't even block-based scoping in the flow analysis above - if in your block there was the code foo = 1; the typescript engine would then expect that foo will behave like a Number at runtime. I haven't dived in deep enough, but I suspect foo could even have different structural type information within the same expression, e.g. a ternary (typeof foo === 'string') ? something(foo) : somethingelse(foo)
- ygra 4y agoIt's expression-based as well This doesn't only apply to conditional expressions, but also things like typeof foo === 'string' && ...
- saghm 4y agoIn my compilers class, this is how we did static downcasting for the toy language we created (I think it was called Oat?). I think it's clever, but I find that I prefer mechanisms that rebind the variable for clarity purposes. It's super easy IMO to accidentally gloss over something as being just a regular if statement rather than something that's actually statically changing the type of the variable compared to having different syntax than just a regular conditional. I also tend to prefer being able to look at a variable's usage and then look back to the time it was initialized and know that it's still the same type; having the type change only within a given scope without any explicit binding seems like something I'd mess up a lot, especially when reviewing code I didn't write myself.
- arthurcolle 4y ago> All the other languages I know of, like this proposal for Java, require the variable to be redefined. can you give a few examples? This makes no sense to me and I've touched quite a few ecosystems in my time. Maybe I am misunderstanding but my current understanding makes this seem not realistic
- gizmo686 4y agoConsider the following pseudo-Java: Object foo = ... if (foo instanceof String) { System.out.println(foo.toLowerCase()) } The above is not valid Java, because foo has type Object, which does not have a toLowerCase() method, so the compiler refuses to compile it. What you need to do is something like: Object foo = ... if (foo instanceof String) { String sfoo = (String) foo System.out.println(sfoo.toLowerCase()) } Granted, you don't necessarily need to explicitly redefine in, as Java allows anonymous casting: Object foo = ... if (foo instanceof String) { System.out.println(((String) foo).toLowerCase()) } Regardless, the point still stands. Java (and most strongly typed languages with an inheritance based type system) requires an explicit cast, even if a straight forward static analysis would show that the object in question is already the desired type.
- GavinBierman 4y agoThis is all true, but with pattern matching for instanceof you don't need the explicit cast: Object foo = ... if (foo instanceof String s) { System.out.println(s.toLowerCase()) } In this case, the scope of the pattern variable `s` is just the then block, so are free to reuse it elsewhere in the block.
- zelphirkalt 4y agoAnother feature, that Java finally adopts, after decades of not adopting it. More and more language features and concepts are being introduced, finally exposing Java-only programmers to them. Lambdas, pattern matching, project Loom, at some point in the future, Java might be called a modern language. Good for Java and Java programmers. Just checked some other languages: - SRFI (Scheme Request for Implementation): https://srfi.schemers.org/srfi-200/srfi-200.html https://srfi.schemers.org/srfi-200/srfi-200.html Syntax: > | ($ record-name pat_1 ... pat_n) a record - GNU Guile: https://www.gnu.org/software/guile/manual/html_node/Pattern-Matching.html https://www.gnu.org/software/guile/manual/html_node/Pattern-... Seems to be implementing the SRFI: > | ($ record-name pat_1 ... pat_n) a record - Rust: https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html and https://doc.rust-lang.org/reference/patterns.html#struct-patterns https://doc.rust-lang.org/reference/patterns.html#struct-pat... > We can also use patterns to destructure structs, enums, and tuples to use different parts of these values. Let’s walk through each value. > Struct patterns match struct values that match all criteria defined by its subpatterns. They are also used to destructure a struct.
- elric 4y ago> Another feature, that Java finally adopts, after decades of not adopting it Java has been steadily moving in this direction for quite a few years now. It's tricky business. There's the language proper, and there's the JVM, which are interdependent. Both are aiming to remain backwards compatible. Introducing new major language features is not an easy feat.
- pavelbr 4y agoC# has been adding new major language features all the time since its inception, including pretty much everything we are seeing being added to Java these days.
- Skinney 4y agoYeah, and this "kitchen sink" approach is partly why I stopped writing C#. Same thing is turning me away from JS. It's exhausting trying to stay up-to-date with the language, in addition to changes in the frameworks and tools on top of the challenges of my day job. I might be showing my age, but I've come to appreciate that features come after long and careful consideration, and not every single release.
- quilombodigital 4y agoI am an old school java developer. Can someone explain me why this is a "feature"? I can remember by counting my fingers the times I had to use "instanceof", and it was some classical reflection/injection/instantiation framework 'vodu'. If you are using instanceof in a normal java program, you are just making it wrong. It looks like some new javascript guys arrived in the block and are trying to make java look something messy like Scala. What real world problem is this trying to solve? Are you just fucking bored because you are all out of ideas and every JDK release we need "something"? Why these JEPs only have "boxes,rectangles,A,B,I" class examples and not a simple real world class scenario? Why we need to make java "friendly" to work with generic objects? it should be hell! I cant wait for JDK 50.
- metadat 4y agoThis doesn't take anything away, it just adds extra convenience and flexibility.
- quilombodigital 4y agoMy problem is with the "just adds"... Scala made this mistake, and ended with many different ways to do the same task. It is a powerful language, but I believe languages must have only one (or two) concise way to to things.
- halfmatthalfcat 4y agoThe "multiple ways of doing things" is a feature, not a bug and something which lives in almost every programming language. It's become a meme at this point and a lazy argument against a language.
- metadat 4y ago> It's become a meme at this point and a lazy argument against a language. This might be a casual dismissal of what is in fact a nuanced aspect of programming languages. Multiple ways of doing the same thing can make the language less accessible because it becomes confusing to reason about what the "right" way is. That said, this particular addition merely reduces repetitive toil, and it follows similar existing Java conventions, for example see the Java-7 equivalent of Python "with-statements". https://stackoverflow.com/a/35116251 https://stackoverflow.com/a/35116251
- Strs2FillMyDrms 4y agoFor everyone saying the visitor pattern requieres switch statements this is false. The visitor can build its case inside the iterface/implemetation. If the complexity is being solved by the "housing" class that the visitor will be visiting you are not doing enough.
- cutler 4y agoDespite recent improvements including raw string literals it beggars belief how Java still requires regex metacharacters to be escaped. Until that's fixed Java is not an option for me.
- deleted 4y ago[deleted]
- eecc 4y ago
- deleted 4y ago[deleted]
- mabbo 4y agoIt's not the feature that excites me, it's the pace of Java language improvements. The 6-month cycle of versions, the whole process of letting experimental features in for a couple versions to test, refine, and likely accept, it's all really working, you know? We're getting stuff that's been so long desired, and it's happening quickly enough to be exciting but not so fast that we're overwhelmed and things are breaking. People often say that Java 8 was the big important update to the language (streams, lambdas, java.time, and more), but I think Java 9, which began this process was the true rebirth of the language.
- quantified 4y agoInterested in your enthusiasm on this front. If (x instanceof String s) was demonstrated at the JavaOne announcing Java 9. The process is proceeding and visible, but finally getting things so long desired and so long available in other languages isn’t supporting much notion of improved pace. Visibility, yes.
- EdwardDiego 4y agoGiven that most Java using companies tend to go LTS version to LTS version, it's not like Java 13.141.59 is going to see massive uptake. And the movement from Java 8 to Java 11 was slowed by bytecode changes and the advent of modules, which declared which of their internals you could dick with. Sure, you could add --add-opens to your commands, but it felt (as intended), hacky as all hell. For an example, I've submitted PRs to Apache Kafka as part of the effort to replace Powermock with Mockito. Why? Because a) Powermock doesn't play nice with with JUnit 5 and b) Because it breaks totally on Java 16+, due to Java modules being enforced by default as of that version. But for companies that are happy to keep up with the faster release schedule, there's been access to new and useful features at every new Java version, and it means that early adopters can provide feedback on new features like record classes etc. before they're crystallised in an LTS version. And given the history of Java improvements between Java 6 and Java 8, the current release cycle is positively blistering in comparison.
- pron 4y ago
- blacksmithgu 4y agoI love seeing modern language features coming to Java! Sadly, I suspect it will be at least a decade before I see Java 19 used anywhere I work...
- peterkelly 4y agoA great paper on this topic: D. J. Pearce, “Sound and Complete Flow Typing with Unions, Intersections and Negations,” in Verification, Model Checking, and Abstract Interpretation, vol. 7737, R. Giacobazzi, J. Berdine, and I. Mastroeni, Eds. Berlin, Heidelberg: Springer Berlin Heidelberg, 2013, pp. 335–354. doi: 10.1007/978-3-642-35873-9_21. https://ecs.wgtn.ac.nz/foswiki/pub/Main/TechnicalReportSeries/ECSTR12-20.pdf https://ecs.wgtn.ac.nz/foswiki/pub/Main/TechnicalReportSerie...
- naruhodo 4y agoGreat! Java is getting destructuring. static void printUpperLeftColoredPoint(Rectangle r) { if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) { System.out.println(ul.c()); } } But lr is not used in this example. Where is the syntax for ignoring a component? Can we drop lr, or use a placeholder like '_' multiple times?
- kaba0 4y ago_ is reserved likely for this use case in advance, but AFAIK it is not yet settled on how exactly should it work. But as always, the language team does an excellent job on improvements which is exceedingly hard while you have to balance backward compatibility.
- jsiaajdsdaa 4y agoI really feel Java already has everything I need to do my work. More improvements are always welcome of course.
- fithisux 4y agoMe too. I do not like records or pattern matching in Java. We have scala/kotlin/groovy for this. Java should have focused more on improving libraries, build infra (Java 9 was good) and changes in syntax focused on the traditional paradigm. If people needed pattern matching. Thy could mix languages. This is huge. I find myself more and more distant from java these days because of this.
- kaba0 4y agoRecords, pattern matching and the like are eons better than POJOs manipulated by reflection. Especially once value types come and many record type can be propagated as a value type, it will also cause huge speed improvements.
- jsiaajdsdaa 4y agoThe demonstration on youtube is really nice. The 500 x 500 matrix of objects showed a really fast speed up. I suspect though that internally theres still a lot of plumbing that needs to be changed on top of what they've already changed. It was the same way for reflection, lambdas, threads. They really do a great job on iterating and keeping backwards compatability too.
- ackfoobar 4y agoComing from Scala, I find positional decomposition a huge mistake. In Scala, constructing a `case class` allows positional/named arguments, which prevents some bugs. But destructring has to be positional. Where is the mathematical beauty of duality? Also if I only want a few fields from a wide record, I have to add the underscores for all other fields. `case SomeClass(_, _, _, field1, field2) =>` And when I add another field, all those `case`s have to be changed. OCaml gets it right. Pattern matching records uses named fields. It has field punning, you only have to write the name once if new variable's name is the field name (like JS). Extra fields are discarded with one single `; _` in the pattern. I don't really care what Java does. I just hope Kotlin won't mess it up.