5 ms·
Interesting. Algebraic Data Types are almost always one of the things I miss when I use imperative languages. I have to do Java at work, and while I've kind o
by tombert 3y ago
Interesting.
Algebraic Data Types are almost always one of the things I miss when I use imperative languages. I have to do Java at work, and while I've kind of come around on Java and I don't think it's quite as bad as I have accused it of being, there's been several dozen instances of "man I wish Java had F#'s discriminated unions".
Obviously I'm aware that you can spoof it with a variety of techniques, and often enums are enough for what you need, but most of those techniques lack the flexibility and terseness of proper ADTs; if nothing else those techniques don't have the sexy pattern matching that you get with a functional language.
This C extension looks pretty sweet since it appears to have the pattern matching I want; I'll see if I can use it for my Arduino projects.
- AlecBG 3y agoSealed interfaces in java 21 allow pattern matching
- tombert 3y agoYeah I know, we just don't use Java 21 at work yet. I'm super excited for that update, and it actually looks like we will be transitioning to that by the end of the year, but I haven't had a chance to play with it just yet. I do find it a little annoying that it's taken so long for Java to get a feature that, in my opinion, was so clearly useful; it feels like they were about a decade later on this than they should have been, but I'll take whatever victories I can get.
- brabel 3y agoIf you can enable preview features, you can use pattern matching since Java 17 (though the final syntax in Java 21 was slightly changed - still you may want to use preview features, it's mostly fine in Java as they tend to change very little, and when you do upgrade, the compiler will tell you where you need to update your code).
- lupire 3y agoKotlin is JVM compatible and has ADTs. Java has https://github.com/functionaljava/functionaljava https://github.com/functionaljava/functionaljava which is unsupported but stable.
- tombert 3y agoSure, and Scala has had ADTs since its inception as well I think, and that's also JVM. It's not ADTs, but Clojure does have some level of pattern matching/destructuring as well. It wasn't that I though that the JVM was incapable of doing something like an ADT, just that vanilla Java didn't support it. While it's easy to say that "companies should just use Kotlin", that's a bit of a big ordeal if you already have a 15 year old codebase that's written in Java. I've heard of but never used the Functional Java library, though it'd be a tough sell to get my work to let me import a library that hasn't been updated in two years.
- Tainnor 3y ago> that's a bit of a big ordeal if you already have a 15 year old codebase that's written in Java. JetBrains has prioritised compatibility with Java and it shows. Of course, there are some gotchas (such as nullability or checked exceptions which don't exist in Kotlin), but you can really mix Kotlin and Java code relatively freely.
- brabel 3y agoJava 21's pattern matching (you don't need functionaljava, and shouldn't really use that unless you're really into FP) is kind of nicer than Kotlin's, because you can automatically "destruct" records in your matches. For Java, see https://www.baeldung.com/java-lts-21-new-features https://www.baeldung.com/java-lts-21-new-features Kotlin's: https://www.baeldung.com/kotlin/when https://www.baeldung.com/kotlin/when Make up your own mind.
- ackfoobar 3y ago> kind of nicer than Kotlin's, because you can automatically "destruct" records in your matches. I find positional destructuring of records a bad idea. https://news.ycombinator.com/item?id=31399737 https://news.ycombinator.com/item?id=31399737
- estebank 3y agoEveryone who hasn't used ADTs and pattern matching doesn't get what the big deal is all about. Everyone who is used to ADTs and pattern matching doesn't get what the big deal is all about, until they have to work in a language that doesn't have them. And everyone who just found out about them can't shut up about them being the best thing since sliced bread. :)
- im3w1l 3y agoI have mainly used them in Rust. They are nice I suppose, but nothing mindblowing. To me it feels very similar to an interface (trait) implemented by a bunch of classes (structs). I have multiple times wondered which of those two approaches would be better in a given situation, often wanting some aspects of both. Being able to exhaustively pattern match is nice. But being able to define my classes in different places is also nice. And being able to define methods on the classes is nice. And defining a function that will only accept particular variant is nice. From my perspective a discriminant vs a vtable pointer is a boring implementation detail the compiler should just figure out for me based on what would be more optimal in a given situation.
- estebank 3y agoEnums are closed sets and trait objects are open sets. They are conceptually related concepts, but the language puts syntactic distance between the two, and I don't think it should. There are lots of open design questions for every feature you propose, but all of them have been discussed and have higher or lower chance of making it into the language.
- im3w1l 3y agoThat's kind of greek to me, but shouldn't promising the compiler that my set is closed unlock more features instead of taking features away?
- estebank 3y agoI'm not sure I understand your point, but I'll elaborate on ways that we could "homogenize" the two features: --- We could add implicit enums to impl Trait, so that you could return different types from a function: fn foo() -> enum impl Display { if rand() > 0.5 { "str" } else { 42 } } which would let you get around the problem of returning a type erased object for a Trait that isn't object safe: trait Trait { const C: i32 = 0; } impl Trait for i32 {} impl Trait for &'static str {} fn foo() -> Box<dyn Trait> { if true { Box::new("") } else { Box::new(42) } } error[E0038]: the trait `Trait` cannot be made into an object --> f500.rs:6:17 | 6 | fn foo() -> Box<dyn Trait> { | ^^^^^^^^^ `Trait` cannot be made into an object | note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety> --> f500.rs:2:11 | 1 | trait Trait { | ----- this trait cannot be made into an object... 2 | const C: i32 = 0; | ^ ...because it contains this associated `const` = help: consider moving `C` to another trait = help: the following types implement the trait, consider defining an enum where each variant holds one of these types, implementing `Trait` for this new enum and using it instead: &'static str i32 --- Relax object safety rules, like making all assoc consts implicitly `where Self: Sized`. --- We could make enum variants types on their own right, allowing you to write fn foo() -> Result<i32, i32>::Ok { Ok(42) } let Ok(val) = foo(); There's some work on this, under the umbrella of "patterns in types". For now the only supported part of it is specifying a value range for integers, but will likely grow to support arbitrary patterns. --- Having a way to express `impl Trait for Enum {}` when every `Enum` variant already implement `Trait` without having to write the whole `impl`. --- Anonymous enums: fn foo() -> Foo | Bar | Baz --- Being able to match on Box<dyn Any> or anonymous enums match foo() { x: Foo => ..., x: Bar => ..., _ => ..., } --- Stop needing to create a new struct type in order to box a single variant enum Foo { Bar(Box<struct { a: i32, b: i32 }>), } --- These are of the top of my head, there are many things that you can do to make trait objects and enums feel closer than they do today, to make changing the way your code works a "gradient" instead of a "jump". My go-to example for this is: if you have a type where every field is Debug, you can derive it. As soon as you add one field that isn't Debug, you have to implement the whole impl for your type. That's a "jump". If we had default values for structs you could still use the derive by specifying a default value in the definition. That makes the syntactic change "distance" be as far as the conceptual change "distance".
- eru 3y ago> Algebraic Data Types are almost always one of the things I miss when I use imperative languages. Algebraic data types and pattern matching actually work really well in imperative languages, too. See eg Rust.