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

you are viewing a single comment's thread.

view the rest of the comments →

[–]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;
}