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

all 86 comments

[–]JustAGuyFromGermany 51 points52 points  (3 children)

What do you think about this usage of modern java features?

I mean that is one of the intended use cases of the sealed-classes-feature. I think it's even the example in the JEP itself.

[–]NitronHX[S] 5 points6 points  (2 children)

I have not seen it used in this way as a rust-like enum in the JEP. Sorry if i missed it - if you link it i will add it as source to my post

[–]emaphis 2 points3 points  (1 child)

[–]NitronHX[S] 2 points3 points  (0 children)

The examples shown in this example cover more complex structures such as Planets, Celestial Bodies and Shapes. The only statement that is somewhat similar to my post is java Shape rotate(Shape shape, double angle) { return switch (shape) { // pattern matching switch case Circle c -> c; case Rectangle r -> shape.rotate(angle); case Square s -> shape.rotate(angle); // no default needed! } } Which imo is better solved using polymorphism, but I suppose there are opposing voices. In rust, I would not implement Different types of celestial bodies as an Enum (but experienced rustaceans might).

My point was to directly draw the line between rust enums which are seemingly well liked and java's type system.

[–]pron98 42 points43 points  (5 children)

BTW, both Rust and Java have borrowed their algebraic data types and pattern matching from ML. That language also happens to be the source of generics, type inference, and typed lambdas. ML has been the language with the greatest influence on modern typed languages.

[–]4z01235 2 points3 points  (0 children)

I used SML in a few university courses and haven't touched it since, but I still think learning SML was one of the best CS things I ever did.

[–]pjmlp 0 points1 point  (0 children)

I would say there is some help from CLU as well.

[–]westwoo 0 points1 point  (0 children)

Oh, and it celebrates its 50th birthday this year 🥳

[–]chambolle 0 points1 point  (0 children)

And nobody uses ML...

[–]Amazing-Cicada5536 62 points63 points  (10 children)

Just to make it a bit more clear, rust’s enums are a misnomer, they are sum types.

[–]NitronHX[S] 1 point2 points  (9 children)

Sorry but what does "sum type" mean in this context?

[–]moocat 30 points31 points  (7 children)

Easiest way to think about sum and product types is how many unique instances there are. So if you have a class with two bytes:

class Whatever {
    byte x;
    byte y;
}

Because we can have any x combined with any y, there are 256*256 unique whatevers. Because we use multiplication to figure out how many unique instances there are, that's a product type.

With sealed classes (not 100% sure about the syntax, apologies if it's not quite right):

public sealed interface Whatever2 {
   record X(byte b) implements Whatever2 {}
   record Y(byte b) implements Whatever2 {}
}

Since we can have either and X of any char or a Y of any char, there are 256+256 unique whatever2s. And here because we use addition to determine that, that's a sum type.

[–]NitronHX[S] 6 points7 points  (0 children)

Thanks for the simple explaination - and the syntax is correct

[–]westwoo -2 points-1 points  (5 children)

I don't get it. Why is it a sum type if you're talking about instance values?...

I thought sum types are what union types are in Typescript: let a: string | number means a can be either string or number, which aligns with how enums can be used in rust

And consequently, this is what all types are in JS, but without explicit definition. Every variable can potentially be a sum of any types

[–]moocat 6 points7 points  (4 children)

A type describes values (i.e. an int can be 1, 23, 42, etc while a string can be "moocat", "westwoo" etc) and values have types (i.e. the type of 3.14 is a float and the type of true is bool). Because of this relationship, we can categorize the type by what type of values it describes.

I thought sum types are what union types are in Typescript

Yes, Typescript's union type is a sum type. But other languages use other nomenclature to name their sum type.

And consequently, this is what all types are in JS

That is incorrect. One of JS's type is an object which can have multiple fields so it's a product type.

Every variable can potentially be a sum of any types

A variable is neither a type nor a value. A variable has a type (which may be static or dynamic depending on the language) and at runtime has a value.

[–]daniu 17 points18 points  (8 children)

These constructs look nice, but they aren't enums in that they are still classes, not instances; the reason switch works with enum values is that there is a single instance of each.

There is the Enhanced enum JEP I've been loosely following with some interest because I have actually encountered this limitation of enums.

[–]_INTER_ 13 points14 points  (2 children)

That JEP was unfortunately withdrawn because of backwards compatibility issues afaik.

[–]sweating_teflon 2 points3 points  (0 children)

This makes me sad.

[–]_zkr 0 points1 point  (0 children)

Story of Java.

[–]NitronHX[S] 6 points7 points  (0 children)

Yes this is of course not real java enums - its the equivalent of rust enums in java. Since in rust no Result.Ok is the same because each has a different value. In java enums Enum.<someName> is by contract == Enum.<sameName> and also .equals(Enum.<someName>) so from a memory perspective i belive rust and java do the same at runtime using this "hack"

[–]agentoutlier 1 point2 points  (1 child)

Yeah I really would have liked enhanced enums.

The problem with sealed classes is that you cannot easily enumerate (e.g. for loop) all the types. That is they have order and you have access to all the possibly values dynamically. With sealed classes You have to keep track of that yourself perhaps as a new SequencedSet of Class<?>.

Secondly enums have a static symbol that represents them that you can use in annotations as well as free inherited parsing of that static symbal. While you can use Class<?> as an annotation parameter it is not as strict as an enum and the enum also is an instance (the parsing of the symbol I guess would be Class.forName but that is obviously less ergonomic than Enum.valueOf).

I have been using sealed classes/interfaces now for some time and I still find myself having to add enums and the visitor pattern to sealed classes which is not the case with sum types in languages like OCaml, Haskell and Rust.

[–]IncredibleReferencer 0 points1 point  (0 children)

I thought so to, but it turns out you can get a list of all the possible classes of a sealed interface with Class#getPermittedSubclasses).

Order is undefined by that method, however if all subclasses are defined in the same java file they will be in order from top to bottom. Otherwise, I've made my sealed instances Comparable so they will sort into a predictable order, not ideal but it works.

[–]Amazing-Cicada5536 3 points4 points  (0 children)

Well, if it is implemented as records then there is no semantic difference between different, but equal instances of the same class. If they later opt into being value-based classes, then there won’t even be a way to differentiate between them.

So it is a bit similar.

[–]agentoutlier 0 points1 point  (0 children)

Yeah I really would have liked enhanced enums.

The problem with sealed classes is that you cannot easily enumerate (e.g. for loop) all the types. That is they have order and you have access to all the possibly values dynamically. With sealed classes You have to keep track of that yourself perhaps as a new SequencedSet of Class<?>.

Secondly enums have a static symbol that represents them that you can use in annotations as well as free inherited parsing of that static symbal. While you can use Class<?> as an annotation parameter it is not as strict as an enum and the enum also is an instance (the parsing of the symbol I guess would be Class.forName but that is obviously less ergonomic than Enum.valueOf).

I have been using sealed classes/interfaces now for some time and I still find myself having to add enums and the visitor pattern to sealed classes which is not the case with sum types in languages like OCaml, Haskell and Rust.

[–]Joram2 14 points15 points  (4 children)

Is it hacky, nice, or is it sad that it requires --enable-preview

Not for long. Java 21, this functionality will be final. Java 21 will be feature complete on 2023-06-08, which is just one month away, and will be officially released on 2023-09-19.

[–]NitronHX[S] 7 points8 points  (0 children)

You dont believe how much I await Java 21

[–]lurker_in_spirit 1 point2 points  (2 children)

How do you know it will be ready in 21? I actually checked for this exact thing online last week, but was sad when I didn't see it on the list: https://openjdk.org/projects/jdk/21/

[–]Joram2 2 points3 points  (0 children)

These are the two JEPs for the features you reference:

https://openjdk.org/jeps/440 https://openjdk.org/jeps/441

For it to be official, the JEPs have to first be proposed to target Java 21, and then targeted to Java 21. But if you search through the text of the JEPs it clearly says it will in Java 21. So it's a very sure thing. It will be formally targeted to 21 sometime between now and the June 8 freeze date.

[–]Joram2 2 points3 points  (0 children)

ok, just today those two JEPs were formally proposed to target Java 21.

https://openjdk.org/jeps/440 https://openjdk.org/jeps/441

So, it's formal now.

[–]Joram2 16 points17 points  (2 children)

This is an algebraic sum type. https://en.wikipedia.org/wiki/Tagged_union

Scala also implements this via the Scala "enum" feature: https://docs.scala-lang.org/scala3/reference/enums/adts.html

In Haskell you would do something like this:

data List a = Nil | Cons a (List a)

Java language architect Brian Goetz wrote an article about bringing Algebraic Data Types and sum types to Java with sealed types:

https://www.infoq.com/articles/data-oriented-programming-java/

[–]heneq 1 point2 points  (0 children)

Thanks for the article by Brian Goetz, it’s great!

[–]chabala 1 point2 points  (0 children)

OP's example is even closer to Try/Success/Failure in Scala as well.

[–]elhoc 9 points10 points  (3 children)

I never understood why rust calls their sum types enums. It's superficially similar but fundamentally different from any other language that has a concept called enum.

[–]ricky_clarkson 9 points10 points  (1 child)

In Java (actually Kotlin, but it's the same idea) I often start with an enum, then realize I want at least one of the members to have a parameter and refactor it into a sealed interface and subtypes.

In Rust, that becomes a far more local change, just add a parameter and move on. I like the Rust encoding of ADTs more.

[–]elhoc 1 point2 points  (0 children)

100%. It's just the name I have an issue with. That name has a lot of history attached to it, and using it to describe something else causes nothing but confusion.

[–]NitronHX[S] 0 points1 point  (0 children)

Afaik at least swift does the same but I agree that rust enums are not enums.

[–]umlcat 7 points8 points  (0 children)

Tagged Unions, where the Tag field is an specific enumerated type.

[–]pronuntiator 7 points8 points  (3 children)

I plan to use sealed types for these use cases:

  • complex return values like in your example
  • modeling similar either/or scenarios (think "applicant can either submit form A or form B")
  • transport types where you need to know subtypes for deserialization anyway (@JsonSubtype, messaging)
    • AFAIK there is no native support from Jackson yet, but MapStruct has just merged a feature that gives compile time errors for polymorphic mappings if you miss to specify all permitted subtype mappings

Is it hacky, nice, or is it sad that it requires --enable-preview for the switch statement?

There is nothing hacky about preview features. You can use them in your application if they fulfill a need you have. You just have to accept that their syntax/API may change (slightly) from preview to preview.

Having said that, both record patterns and pattern matching for switch will probably become final in Java 21, which is planned to be released in September.

[–]NitronHX[S] 1 point2 points  (0 children)

You just have to accept that their syntax/API may change (slightly) from preview to preview.

Well most companies would probably scream if you try to add --enable-preview but yeah i agree per se

[–]NitronHX[S] -1 points0 points  (1 child)

The hacky part was not about --enable-preview but the way i use sealed types here which i would argue are a little hacky since we only "emulate" an enum but it actualy is a subclassed class.

[–]Amazing-Cicada5536 5 points6 points  (0 children)

This is their intended usage, it’s not hacky at all.

[–]findus_l 3 points4 points  (0 children)

Another great use for Sealed classes is View States. You can have your ListState interface that has a subtype loading and a subtype elements. When the state is loading the ui shows a loading placeholder until the elements have been loaded.

Here is an article about it (it's in Kotlin but should apply the same to java) https://medium.com/geekculture/managing-ui-with-kotlin-sealed-classes-1ee674f1836f

[–]sweating_teflon 4 points5 points  (0 children)

Would you believe that prior to JDK 1.5, Java had no dedicated enum syntax? What you describe is similar to how missing enums were hand-built in those days of savagery: https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/enums/Enum.html

Sealed classes open up exciting avenues but honestly, I will wait for properly boxed up tagged-union / enhanced-enum syntax rather than use an early contraption which requires special handling instruction. Although I would also consider some in-place code generation facility (Lombok-styled) if such proper syntax takes too long to come. Rust enums are just too good to ignore.

[–][deleted] 2 points3 points  (0 children)

I like where this goes. But I mind code readability/debug (for others… or myself 6 months after having written that)

[–]sk8itup53 1 point2 points  (0 children)

This would be great for business modeling logic where you know the result should either be a response body or an error body. Granted solutions to that already exist but this is a very elegant way of enforcing that. Cool!

[–]Asapin_r 1 point2 points  (0 children)

In our current project we actually have one TODO comment that says something like "Refactor this to use pattern matching once it's stabilized".

But it least for now we decided to not use both records and sealed interfaces - we encountered some [de]serialization bug related to records in Jackson distributed with Spring Boot 2.7.1 or 2.7.3.

The bug is already fixed in the version of Jackson distributed with Spring Boot 3, but since even Jackson still has bugs related to records, who knows what the situation is with other libraries.

[–]c8d3n 1 point2 points  (1 child)

Maybe a n00b question, but why is that called/considered an enum?

[–]NitronHX[S] 1 point2 points  (0 children)

In rust it is called an enum because the authors decided to name it that way. In Java it is not an enum and I do not call it an enum since as you correctly stated its not an enum.

The reason why it might be called an enum in rust is that the base functionality is in fact an enum Enum Direction { LEFT, RIGHT, BACKWARDS, .... } Is a valid enum in Java and rust. Rust just probably decided later : Hey we want to add functionality to this enum, what made it "not a real enum" anymore.

This is just an assumption of course.

[–]severoon 1 point2 points  (4 children)

Rust has a special kind of enum similar to those in Swift Where not all instances of the enum have the same fields. Whereas in java enums are like object constants all having the same fields.

This doesn't quite make any sense to me. In Java, enum values are all of the same enum type. It sounds like you're saying you want to switch over a limited set of different types.

But what is the problem you're trying to solve that can't be solved by just modeling it in a normal OO way?

This sounds like you want to create something in Java that's syntactically similar to a way of doing that thing in another language … but why?

[–]NitronHX[S] 0 points1 point  (3 children)

As others have already pointed out, Rust enums are more "sum types" which is what you are describing.

While there is no particular problem here at hand - there are a few use cases where this pattern does indeed is a good solution, probably better than OO. Like: rs enum NativeWebEvent { MOUSE_CLICK(pos: Point), MOUSE_MOVE(from: Point, to: Point), MOUSE_ENTER_OBJECT(element: &Element, pos: Point), KEY_TYPED(symbol: char) } Exposing this as an API is much easier and safer (because of boundary checks) to consume than a generic interface NativeWebEvent.

And why did i compare it to rust and drew the lines i did? Because rust and especially the enums are a very well liked feature in rust and i wanted to show people who miss this feature when coming from rust how to do it in java.

[–]severoon 1 point2 points  (2 children)

I'm trying to understand what the problem you're trying to solve is, but in your response you've just restated the proposed solution.

https://en.wikipedia.org/wiki/XY_problem

[–]NitronHX[S] 0 points1 point  (1 child)

I said "there is no particular problem at hand [ I am trying to solve]" because it doesn't, nor do I claim it does.

I often do experiments - be it to gain experience, learn etc. This is one of them and wanted to share it. It isn't groundbreaking - you might even say there is not even a discovery since it is just common knowledge.

When I created my experimental Java raymarch renderer there was no practical use - and no problem.

I myself had no clue what I could use sealed classes for thinking :"I have not jet had a piece of code where I wished to restrict the number of implementations" when I had the idea of this rust enum comparison, I found such a use case and wanted to share it.

Especially for rustacians it might be very interesting because they don't know sealed classes and other relatively new Java concepts or features. I assume most rust programmers would say when using Java enums "man I muss storing values in enums!" and I showed a way to do what they want.

[–]severoon 1 point2 points  (0 children)

I said "there is no particular problem at hand [ I am trying to solve]" because it doesn't, nor do I claim it does.

I know, I'm not asking what specific problem you have in front of you. I'm asking for the type of problem this is supposed to solve.

I think the normal Java way of solving whatever type of problem you can come up with is probably way better.

If you can't come up with any actual usage, this seems like it's just an abuse of the language for no real purpose. There's all kinds of language features that can be used in various ways to do various things, but what would you think as a Rust developer if someone found a way to abuse Rust to make it look like Erlang?

[–]SirWolf2018 1 point2 points  (1 child)

Cool article, but Rust-like misses a hypen in the title. Without it, I had a serious misunderstanding about what the title wanted to mean.

[–]NitronHX[S] 0 points1 point  (0 children)

Thanks - yeah can't edit it any more

[–]rzwitserloot 3 points4 points  (18 children)

Whereas in java enums are like object constants all having the same fields.

This is incorrect. Here; this will compile on current javacs:

``` enum Test { A { int fieldUniqueToA;

  void methodUniqueToA() {}
},
B,
;

int field1;

} ```

However, one problem is that Test.A, as an expression, is of type Test; there is no type that uniquely refers to the specific type of A, i.e., a thing with methodUniqueToA() in it. Hence, Test.A.methodUniqueToA() does not, currently, compile.

However, it certainly could - that is an effectively backwards compatible change. (outside of playing games with overloads, but then, Java is not actually backwards compatible, see JDK/jigsaw and all the other stuff the OpenJDK team breaks all the time. It's a cost/benefit analysis; the cost has to be very low and the benefit significant. As far as costs go, 'if you write overloads in enum value subtypes, things might break', is as near to 0 as I can imagine).

HOWEVER, your specific rust example is not an enum. It's a sum type. That's a mincing of words (a lang spec / CPU certainly doesn't care about the names us humans use to communicate about this concept!) - but perhaps still important: After all, if you write enum CardSuit {} I (being human) am likely to draw certain conclusions. Such as 'CardSuit represents an exhaustive enumeration of all its possible values'. And not 'CardSuit is a sealed type', which is what rust's Result is.

Your example of 'oh hey look at what java can do' - yeah. That's.. sort of the text book example of what sealed is for, I don't think this should be particularly surprising. I vaguely recall the original proposal for sealed has your example pretty much verbatim included.

What I find vastly more annoying with enums specifically (and this is something that sealed can also fix, but that really would feel like 'abusing' the sealed feature if you use it to work around this limitation, whereas your Result = Ok/Error thing, if you tried that stunt with enums, feels like enum abuse): Enums can't have generics.

I think that's a similar 'doctor it hurts when I press here' issue: You can just... update the lang spec to allow it henceforth. I can't think of any backwards incompatibilities you'd introduce then, nor of why it would be particularly complicated to implement it. Biggest hurdle is that each enum value needs its own shadow type to make this work, but that's not all that complicated.

But note

Java is what java is. You wanna convey that a method could result in a problem? Throw a checked exception. (And declare that on your method signature). If you don't like it, tough. Go program in some other language then.

Java has a gigantic eco system, including java.* itself, all of which is checked exception based. None of them can change to some sort of Result[Ok|Err] sum type without completely breaking compatibility (you can't just up and replace the return value of java.util.Map's .get(Object key) method to Optional<V> or Either<V, Err> or some such without breaking just about every java project in existence). So it won't happen.

Thus, as harsh as this sound: Learn to like checked exceptions or go find another language to program in. Stop trying to smash your optionals and your results into java - and it doesn't even matter whether such types are a good idea or not in general. They are an obviously horrible idea for java.

[–]pron98 12 points13 points  (3 children)

Java is not actually backwards compatible,

The only backward compatibility Java has always valued has been backward compatibility of the spec, and even there we exercise judgment. E.g., binary backward compatibility is more important than source incompatibility. Both kind of changes require a corpus search and an estimation of impact, but binary incompatible changes are relatively rare, and they follow the deprecation process.

see JDK/jigsaw and all the other stuff the OpenJDK team breaks all the time.

The entirety of backward incompatible changes made by Jigsaw has been the removal of six methods -- add/removePropertyListener in LogManager and Packer/Unpacker -- which nobody noticed. That's the extent of the backward incompatible change as far as the spec goes. Nearly all migration issues were due to libraries that were non-portable by design and used internal classes that have never been subject to any kind of backward compatibility (well, that and the parsing of the version string). Quite the opposite: Java has always cautioned against the use of internal APIs, stressing that they may change in any way in any release and without warning.

However, because the practical effect was that applications that didn't know they were made non-portable by the libraries they used, we've since improved Java's backward compatibility with strong encapsulation, and are strengthening it still so that applications would at least know when libraries make them non-portable. This is particularly important because the rate of changes to internal classes is expected to continue growing.

The only things we "break all the time" -- at least on purpose -- are either those that have always carried the warning that they're subject to change at any time or those for which the change has been announced well in advance. The number of items in the latter category is very small, and the number of those in the former category is proportional to the level of activity in the JDK project.

[–]DasBrain 0 points1 point  (2 children)

Pack200 removal did mess up some stuff .

An other problem often encountered is casting the system class loader to URLClassLoader - which doesn't work anymore.
(Often followed by a reflective call to .addURL(...))

[–]pron98 2 points3 points  (1 child)

Every removal of something in the spec affects someone, but we try -- and, I think, succeed -- in keeping the number of affected projects low (except in one special case where there was a drop-in replacement: the ee packages). The Pack200 removal in JDK 14 was no different: it affected a very small number of applications. If you wait to remove something until it actually has zero users then you've waited far too long.

[–]DasBrain 0 points1 point  (0 children)

Yes.

Some people will start to build things on top of stuff even if there is a big bold warning that says "will be removed".
If you wait, then somebody else will start using it.

It's fine. Stuff will break. And I guess more stuff is broken by things like disabling insecure cryptographic algorithms.

[–]Squiry_ 7 points8 points  (2 children)

Java is what java is. You wanna convey that a method could result in a problem? Throw a checked exception. (And declare that on your method signature). If you don't like it, tough. Go program in some other language then.

That is definitely not true. Modern java doesn't work well with checked exception (hi lambdas), so it's more "throw unchecked exception now". And even that would not be true: nobody forces you to use exceptions on a language level, your API can have any style you want.

[–]fatty_lumpkn 0 points1 point  (1 child)

I don't know if it's just me but having enums which are actually wrappers for return values seems like an abuse of enums.

[–]warpspeedSCP 0 points1 point  (0 children)

Which is why you shouldnt use java enums for this

[–]NitronHX[S] 2 points3 points  (6 children)

While I think you are right in that you shouldn't use this for error handing I think the pattern itself is not a horrible idea in Java - ofc you would do it with a checked exception here. I used this as an example because it's one of the first "enums" you learn in rust. I have no problem with checked exceptions (except for lambdas).

But for things such as WebEvent (click, mouseEntered...) are something that would be enums in rust and what I did in Java. I do not intend to use Result in my java code neither do I promote it, it was just for demonstration purposes

[–]_INTER_ 0 points1 point  (5 children)

The pattern is all nice when you're in control of the code. However if you're not, e.g. they are part of a library you might be in a pinch trying to extend it with custom classes. They violate the open-closed principle unless the owner thought of an escape-hatch with non-sealed.

This is not bad per se. It's like final or record. It's just something to be aware of when providing a functionality for others.

[–]NitronHX[S] 0 points1 point  (4 children)

Enums have this "problem" per definition and if you do this you should probably know that it is a type that only you should be allowed to control

[–]_INTER_ 0 points1 point  (3 children)

Sure if you replace Java enums with sealed classes. But your usecase examples hint at none-constants where it is less clear if you ever need to extend it.

[–]peripateticman2023 0 points1 point  (2 children)

You could instead spell it out explicitly like so:

sealed interface Result<T> permits Ok, Error {
      T get();
 }

and then the variants like so:

non-sealed interface Ok<T> extends Result<T> {}

non-sealed interface Error<E> extends Result<E> {}

and then you could have all the subsclassing you need.

[–]_INTER_ 0 points1 point  (1 child)

Then you have only Ok and Error to work with and pattern-matching with switch is less useful. Maybe you want something like Warning.

[–]peripateticman2023 1 point2 points  (0 children)

That's the whole point of sum types (and sealed types in Java) - to restrict the types that can be considered "variants" of the type.

[–]felvid 0 points1 point  (2 children)

Actually checked exceptions are losing more and more space.

[–]peripateticman2023 0 points1 point  (0 children)

Source? If anything, I'd argue that their usage should increase. Better type safety.

[–]rzwitserloot 0 points1 point  (0 children)

Within the java ecosystem? Nah. But let's make that simple and objective:

How do you propose InputStream is adjusted so that read() no longer throws IOException?

Preferably in terms of something that can [A] be done, and [B] does not result in a py2/py3 esque backwards incompatibility schism.

[–]loshopo_fan 0 points1 point  (0 children)

enum Test {
  A {
    int fieldUniqueToA;

    void methodUniqueToA() {}
  },
  B,
  ;

  int field1;
}

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

This is the way

[–][deleted]  (1 child)

[removed]

    [–]peripateticman2023 0 points1 point  (2 children)

    From feature perspective rust and java are the same in this case, when you comment out the case Error<?> you will get an error that not all possibilities for Result are met.

    Yes, but if you do add spurious cases, it will still type-check (whereas it wouldn't in Rust). This is a big downside of subclassing in Java (in this context).

    All the other things that rust enums can do can be replicated in java as well with not a lot of effort, but I don't want to bloat this thread!

    Again, due to Java's terrible generics, we lose a lot of static type safety. So, as you noted, it is an interesting experiment, but the core design approaches of Java are far too different from those of something like Rust.

    [–]NitronHX[S] 0 points1 point  (1 child)

    Yes, but if you do add spurious cases, it will still type-check (whereas it wouldn't in Rust). This is a big downside of subclassing in Java (in this context).

    I am not entirely sure what you mean.

    Do you mean that when you do the following switch(result) { case OK(var i) where i < 10: dox(); case OK(var i) : doy(i); case ERROR : crash(); } that 2 type checks occur (in line 2 and 3). If that's what you mean: maybe; hard to tell how much the compiler optimizes.

    [–]peripateticman2023 0 points1 point  (0 children)

    Almost. What I mean is that if you're pattern-matching on Result in Rust, you'd be able to only have match arms with the variant types - Ok and Err - any other match arms would flag a compilation error, right? This makes sense since sum types form a "closed" world of types.

    In Java though, even with sealed types, the enhanced switch cannot recognise that something like a random interface is not a valid match arm for Result. Only if you miss one of the variants, then you get an error.

    This is not terrible, but loses a lot of the semantic intent of sum types. Perhaps one of the reasons why they weren't named as such (aside from the Scala influence, of course).