Optionals that throw NPEs, Streams that operate on more than 1 item when called with .limit(1). Futures that don't cancel. Lambdas that don't play nicely with exceptions. Non-extendable streams.
You are not wrong about cancelabel futures, though.
public sealed interface Maybe<T> {
<T2> Maybe<T2> map(Function<T, T2> mapper);
<T2> Maybe<T2> flatMap(Function<T, Maybe<T2>> mapper);
T elseGet(T fallback);
record Just<T>(T value) implements Maybe<T> {
public <T2> Maybe<T2> map(Function<T, T2> mapper) {
return new Just<T2>(mapper.apply(value));
}
public <T2> Maybe<T2> flatMap(Function<T, Maybe<T2>> mapper) {
return mapper.apply(value);
}
public T elseGet(T fallback) {
return value;
}
}
record Empty<T>() implements Maybe<T> {
public <T2> Maybe<T2> map(Function<T, T2> mapper) {
return new Empty<>();
}
public <T2> Maybe<T2> flatMap(Function<T, Maybe<T2>> mapper) {
return new Empty<>();
}
public T elseGet(T fallback) {
return fallback;
}
}
}
public void demo() {
Maybe<String> foo = new Maybe.Empty<String>();
System.out.println(
switch (foo) {
case Maybe.Just(String val) -> "Hello " + val;
case Maybe.Empty() -> "Fine, leave me hanging";
}
);
}; Maybe<String> foo = null;
foo.map(s -> "bar");
explicit pattern matching is usually discouraged anyway though, and you can do this now with Optional Optional<String> foo = Optional.empty();
String message = foo.map(s -> "Hello " + s).orElse("Fine, leave me hanging");
Other languages like Scala also have an Option.fold method for this specific case.The best you will get is a warning and the JVM will deoptimize Optional back to a regular object, because somewhere someone set an Optional to null somewhere in a library. It doesn't even have to be as obvious as Optional<String> o = null;
Any cast to Optional can let a null pointer slip from an Object reference. List<Optional<String>> is allowed to contain null pointers and you are allowed to assign o = list.get(0) which generates an implicit cast.
There is no good solution for this. Due to the way generics are implemented.
However, unlike the binary choice of class/value type, project valhalla will provide a more granular approach, coming with incremental performance benefits and constraints.
In the current spec draft, the type system boils down to 4 categories:
- Fully mutable, polymorph classes
- Immutable, monomorphic classes
- Immutable, monomorphic classes + null-hostile
- Immutable, monomorphic classes + null-hostile + multithreading-unsafe
Another story for nullability is being investigated as well which would allow for full performance gain as well as full backwards compatibility.
The way Pattern matching works right now is just the beginning.
It's not a coincidence that many languages that PL fans think do things "right" end up being far less popular than languages that PL fans think do things "wrong". Developers have different and conflicting preferences, and they are not distributed evenly. For example, the sweet spot for mainstream, super-popular languages over more/less compile-time checking in some areas vs. language complexity is probably not the sweet spot preferred by most developers. It's a little like the VHS vs. Betamax debate.
Improving a super-popular language in some small way that doesn't harm its popularity can have a far bigger impact on software quality than features that complicate the language to a point where it will not be taught as a first language in a lot of schools and will thus never be super-popular. Designing language features for industry to maximise their impact requires far more complex considerations than just PL theory.
I do like the Java platform in general, but this sort of argument from hypothetical future Java fixes strikes my as incredibly disingenuous. People aren't programming in a hypothetical future Java.
I can literally pose no argument against your imagined solution to this problem without very specific details. (Implication being, it's a straw man at best.)
And saying saying stuff like (in an adjacent thread):
> You're assuming that deconstructing and exhaustive patterns will continue to be restricted to ADTs only. That may or may not be the case.
Is just complete fiction. (EDIT: Are you going to solve the halting problem, or are you going to provide details before just promising "it'll be ok"? I assume the Halting Problem is out of the question, but what's the plan, then? Details, pls)
Either point to extant JEPs or explain in detail what the exact plan is, please.
Having said that, we have a few experiments with type-safe "checked exception transparency" for lambdas (and methods in general), but as always we like to sit on them for a few years because the cost of a bad feature may be much higher than the cost of a missing one. We only publicly discuss specific solutions once there's something to be gained by such a discussion and once we've decided that the problem merits a solution in the short term.
> Is just complete fiction.
I was responding to a statement about deprecating Optional in favour of an ADT, and hinted at this design, which is under exploration (https://openjdk.org/projects/amber/design-notes/patterns/pat...):
case Optional.empty() -> ...
case Optional.of(var x) -> ...
It's not complete fiction, this is actual stuff we're working on, but not everything we explore will end up in the language.I think that Java suffers from NIH syndrome. Nothing that other languages do is good enough for Java until it has been debated for years and reimplemented using new patterns and unknown terminology.
Unfortunately, since Java 9 the rate of change seems to have increased a lot.
> You don't need to waste time on language churn, can spend the entire time building stuff instead.
In my experience, every time you upgrade to a new Java LTS, either something breaks or a library you depend on is incompatible with that Java LTS, and you have to waste some time fixing the breakage (the most recent one I saw: latest Mockito is incompatible with Java 21, unless you set a magic system property).
That was not the case a couple of days ago when I last looked; I see now that they released Mockito 5.7.0 two days ago, which probably fixes this issue. But that still shows my point: for every new Java LTS, you have to either fix some breakage, or update some library you depend on to a new version (which might then require further changes to your code, or even dropping compatibility with some older Java LTS).
Edit: I just tried with the latest Mockito (5.7.0), and it still gave me the same "Java 21 (65) is not supported by the current version" error, when run without the magic system property. It seems something else earlier in the dependencies had a dependency on an older release of byte-buddy, so I will have to manually upgrade byte-buddy, and hope that it doesn't break that earlier dependency.
I needed to bump the version of guice, but beyond that it was a very smooth transition.
I upgraded to JDK 21 on the day of release and haven't had issues with mockito (or spring, hibernate, junit, etc.)
The only feature that really got obsoleted is the Security Manager and the underscore variable name. Primitive wrapper class constructors are on the chopping block next. Most other things are merely being restricted to harden the platform, with ample time to prepare.
Edit: LTS hopping is probably an unwise strategy as one will harvest only disadvantages: few updates, no performance improvements, no new features, and complicated migrations when it is time to upgrade. One best upgrades as soon as the new version becomes available (as people did before Java 8) or stays on LTS until the software is retired.
Since Java 9, they don't introduce changes, they introduce new features which is a bit different.
IT is a field that (should) attract people that are for fast paced environments, not old school slow-to-innovate industries.
1. The rate of change that the industry demands of super-popular languages is not the same rate that PL fans demand. Java innovates a lot in, e.g., GC algorithms and low-overhead profiling because there's more demand for more rapid innovation in those areas than in language features. Java's strategy from day one is to have an innovative runtime and a conservative language (James Gosling called it "a wolf in sheep's clothing") under the assumption that that's what the industry wants. So far, that strategy has worked very well.
2. Because Java will likely continue to be super-popular for many years to come, it's more important to avoid introducing harmful features than to introduce beneficial ones. If you've introduced a "good" feature five years after a less popular language introduced it, you've only lost five years of possible slightly better productivity; if you're introducing a "harmful" feature, you've harmed your language for decades to come. Super successful language need to think more more long-term than languages vying for more users right now.
However languages are not startup companies trying to sell a product, it's the other way around. The popular ones are largely driven by industry to suit the industry needs, which are largely very conservative. Nobody wants another Perl 6 or Python 3.
Another example that people often ask about is named and default parameters. It looks like lots of languages have them, but the problem is that it's usually implemented in a way that harms those languages. For example, C#, Kotlin, and Swift all generally try to support separate compilation and binary compatibility, but their named & default parameters break that. We'll only add the feature to Java once we know how to do it right (something that I think no other language that cares about separate compilation has done yet), and we have some ideas.
And I disagree that FP features are awkward to use in Java (well, some pure-functional theorems don't work because of null, but they also don't work because of reflection, anyway, and they don't work in most non-pure-FP languages, either).
> And I disagree that FP features are awkward to use in Java
Two words: checked exceptions.
A feature contributes such value ones there's some reasonable combination of productivity benefit and demand, and that combination differs over time; the same feature can have more value in, say, 2015 than in 2005. The difference can be because the kind of software people write is different, the hardware is different, or fashion is different (which affects demand).
> Two words: checked exceptions.
Checked exceptions come into play primarily when there's IO or other side effects involved, so we're already in an area that isn't entirely functional and there are special challenges with FP, anyway. Checked exceptions are rare in pure computation (and arise there primarily around parsing strings). I fully acknowledge there's a problem in the interaction of checked exceptions and functional combinators (and we're exploring solutions), but while it makes functional composition of IO cumbersome, I wouldn't go so far as to say that it makes FP awkward.
BTW, while it is certainly not as elegant as it could be, combining IO with FP is already much better in JDK 21:
<T> Result<T> doManyIOTasks(List<Callable<T>> ioTasks) throws MyException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var ts = ioTasks.stream().map(scope::fork).toList();
scope.join().throwIfFailed(this::handleException); // handle/rethrow exceptions
// all IO is done & no more checked exceptions here; we can now do functional processing
return ts.stream().map(Supplier::get). ...;
}
}You're acting as if Java is just reacting to what people are demanding - but I think that Java has been actively shaping how people learn programming for several decades now, and so should take responsibility for it.
> I fully acknowledge there's a problem in the interaction of checked exceptions and functional combinators (and we're exploring solutions)
Why does it take Java 10 years to fix something that other languages have done correctly from day 1? Swift has "rethrows", what's wrong with just adopting a similar approach?
Obviously it's a combination of both, but education about programming paradigms is not the ultimate goal. All features are there to serve the goal of producing working software. Of course, we add new features to serve that goal and to use them requires education but there's only so much you can educate your market to change their habits. You have to do it slowly, as we're doing now with ADTs. Also, PL fans tend to overestimate the actual impact of language paradigms. For example, for years Haskell fans have said that their paradigm leads to significantly more correct programs, but research hasn't found much evidence to support that. For various theoretical and practical reasons (some of which were predicted long ago), programming languages now see diminishing returns, and productivity differences between languages in similar domains are not that big.
The other languages in Java's very small club of super-popular languages are JS and Python (and to a lesser degree C and C++). They're not very innovative language-wise, either, because you can't be unfamiliar and popular. The under-20-year-old languages that are doing very well are TypeScript (which does innovate in the language but in the field of how to add gradual typing to JS) and Go (which is less innovative than Java).
Trying out novel language approaches and seeing how much of an impact they actually make (which is growing ever smaller) is the job for less popular languages. We try to pick ideas that have proven themselves after years of practice.
> Why does it take Java 10 years to fix something that other languages have done correctly from day 1?
Sometimes it's because they haven't done it correctly enough and the problem is harder than it seems, and sometimes we just have more urgent things to do.
> Swift has "rethrows", what's wrong with just adopting a similar approach?
Maybe nothing; maybe the fact that it doesn't work with streams (where exceptions thrown by lambdas aren't thrown by the intermediate operators but by the terminal operation). But whether its something like this or something completely different, we have more urgent things to work on.
Except, you know, that thing Steele said about dragging C programmers halway to Lisp, which can be seen as describing and educational attempt.