It needs close parenting. Java has been ruined by the push to include everyone's pet feature.
It needs close parenting. Java has been ruined by the push to include everyone's pet feature.
Isn't that C#? Java is very slow at adding new features, Java has only things that were proved to work in other languages.
Those surely are not the proof that Java adds "everybody's favorite feature". I think the parent means the newest Oracle projects (Valhalla, modules, value types, streams, and so on).
I do appreciate, though, that when Microsoft decided to do generics for C#, they did so decisively. These days, when C# gets a new feature, it seems like it's the complete opposite of decisively delivered.
class A {};
class A<T> : A {};
I don't mind that. It can even be an aid to organization - all the generic stuff goes in the generic class, all the stuff that doesn't rely on that can go in the base class. But it would be nice to use something like <?>. Too bad generics don't inherit implicit casts, like A<int> to A<object>.There are performance implications to type erasure, to be sure, but when our computers are mostly all future machines from beyond the moon, I'm more interested in minimizing the impedance between my brain and a solved problem.
http://gafter.blogspot.com/2006/12/super-type-tokens.html
At its root, the real problem here isn't "reification good" vs "reification bad", per se. Haskell has an excellent implementation of generics, and erases types far more aggressively than Java does. C# also has a very good implementation of generics, this time based on reification.
The problem is more that Java's particular mix of design decisions resulted in a language that operates at cross purposes with itself. Once upon a time, back in the beginning, Java was a reflective language. Being reflective requires type information to be available at run time, though. When Java decided to use type erasure in its implementation of generics, they created a really bad set of interactions: They kneecapped reflection, so now you can no longer call Java truly reflective; it's only partially reflective. You can no longer effectively and accurately reflect on what have come to be some of the most-used classes in the language. And, at the same time, they forever sealed a rather important corner of the type hierarchy off from generics. They also delayed a bunch of type checking until run time - after types have been erased - so that certain things can just never be made to cleanly type check. Meaning you also can't say Java is any more than partially generic.
Oracle is moving to a faster cycle of development. There are some of us who strongly feel that some of their decisions are based less on what's best for the language and more on catering to the popular-and-loud crowd. I'll never forgive the addition of `var` to the language.
I'm inexperienced with Java and didn't know this existed until I saw your post. It seems like a nice shorthand to me. Can you explain why you don't like it?
And there you have the answer to why Java hasn't evolved that much, or when it did, why it needed to care deeply about backwards compatibility at the source level. It's because Java developers want it that way.
The irony is that people are now abusing "aspects" and "dependency injection" via frameworks like Spring that bring everything but the kitchen sink, but then the language becomes effectively dynamic, as via those annotations all static type safety goes out the window.
Therefore I find it interesting when Java developers complain about Var, because the ecosystem has in my opinion bigger problems. Compared with annotations Var isn't a problem because Var is statically checked, so here we have a clear case of missing the forest from the trees.
You're right, there are loads of conservative Java developers. It's one of the the things that makes me love using the language.
> The irony is that people are now abusing "aspects" and "dependency injection" via frameworks like Spring that bring everything but the kitchen sink, but then the language becomes effectively dynamic, as via those annotations all static type safety goes out the window.
> Misconceptions mostly.
But drop the strawman argument and borderline ad hominem. It'll do you better.
Consider these contrived lines of code:
```
String first = someMethodCall();
var second = someMethodCall();
```
The first provides more useful information at a glance. I don't see any value in the "nice shorthand." Typing out "SomeStupidClassName" has never once been a material bottleneck in my 15+ years of programming, but now we have this new option that caters to the lazy, and in doing so makes life harder. Now I have to either ban it, embrace it, or come up with some ruleset around when you can and can't use it. Why? Someone can't be bothered to type a few extra characters.
It reminds of my grandfather, a former professional ball player, but one who played back in the days where there weren't these multi-million dollar celebrity ballplayers pissing and moaning in the press about just how hard their life is. He used to call those types "high-priced cry-babies," and I really feel a tinge of that in dealing with folks who just wholly embrace `var` and give folks like me shit for having criticisms of it. Perhaps that's just my old blue-collar showing but your convenience in writing a handful of characters simple will never enter into my considerations.
I love using Java, I love the addition of things like streams, the Optional type, etc. My sibling comment is a little right, and very wrong. Lots of Java developers have a certain conservatism about them, I'm mostly certainly one. But there are large reasons to hate it.
> [D]rop the strawman argument and borderline ad hominem. It'll do you better.
There are a few rather glaring spots that I've noticed.
First, when you're refactoring, you've now got to edit every spot where a variable of that type is created. At the very least, when you're just renaming a class, your IDE can help you, but you still create a lot of diff noise. At worst, when you're splitting up a class or otherwise shifting responsibilities, you may end up with a whole lot of yak to shave. This is not just an annoyance; it's a latent code quality problem, because it creates a disincentive to clean things up.
Second, I've seen it become an impediment to writing clean code in the first place. I have encountered situations where it's clear that the author wrote
someMethodCall(
withOutputOfSomeOtherMethod(
thatTransformsTheOutputOfYetAnotherThing(
basedOnThisInput)));
because creating intermediate variables would have meant having to type out (and burn precious screen real estate on) some ridiculous set of 60-character generic type names.I've even seen it result in situations where data gets copied or otherwise processed excessively, because the explicit type annotation resulted in an upcast that shed some useful feature that a subsequent developer shimmed back in because they trusted the explicit type annotation and not a function's actual return type.
So yeah, I decry your assertion that this feature is about being lazy. This feature is, at least for me, about code quality.
If a language encourages bad patterns by making good patterns overly verbose, the language should address that.
OurConfabulatorWithADashOfSpice confab = new OurConfabulatorWithADashOfSpice();
Versus: var confab = new OurConfabulatorWithADashOfSpice();I really respect the slowness of the go maintainers in adding new stuff. I also suggest that we all ponder our tooling some; Writing java with emacs or vi is a materially different experience than using eclipse or idea and var style type-inference seems almost silly with those tools which do it for you.
It's not so much the extra typing that's the problem, it's the extra reading. All the stuttering is visual noise.
But they still somehow keep finding ways to make them not work so well when implemented in Java.
C# may move faster, but its design team is also much more methodical about ensuring that new features have good ergonomics. In Java, I tend to feel surrounded by hacks that were hastily slapped on in an effort to keep up with C# and, increasingly, Kotlin.
I appreciate that by moving faster they get more stuff into more hands faster, but they definitely have a lot of hackish solutions with poor ergnomics outside of the narrow scope they were originally intended for.
If you will: the language features have a clear purpose but a general implementation; and outside of the narrow purpose the designs usually feel pretty poor.
E.g.:
- LINQ/expression trees don't support most of the C# language, and new language features are usually without equivalent expression tree. This isn't a full lisp or F# style quotation, but a pretty narrow window that's not easy to use outside of linq-to-sql style usages.
- LINQ trees are again intrinsically inefficient, since the expression trees compile not to a statically shared expression, but to a bunch of constructors (i.e. looping over even a medium sized expression is bound to be slow); and they're not equatable, so it takes a lot of effort for a consumer to detect this case leading to overly complex (and hard to reproduce correctly) hacks inside stuff like EF.
- LINQ is restricted, but the restrictions are fixed, not customizable. That makes it a poor fit for DSLs, including stuff like Entity Framework, because there are usually lots of expressions your DSL can't support, but there's no way of communicating that to the user. Also, if you use expressions as DSL, you need to follow C# semantics, which isn't trivial; witness gotchas in ORMs surround dealing with null and equality.
- lambdas are either delegates or expressions; not both, and this isn't resolved via the normal type system, but by special compiler rules, making it hard to do both, and leading to type inference issues such as that var f = (int a) => a + 1; cannot compile.
- Roslyn: very poorly documented, and ironically very dynamically typed to the point that many casts or type-switches are necessary but finding out what types there are and what they do is generally a matter of trial and error since the docs aren't great. Ergonomics are poor in other ways too; e.g. dotnet is xplat, but the build-api is not - i.e. it's clearly not dogfooded. Also: totally not integrated with expression trees, which is at least mildly surprising.
- string interpolations are unfortunately quite restrictive (compare with e.g. javascript, where this was implememented much better), and intrinsically and unnecessarily inefficient (at least 2 extra heap allocations, and usually lots of boxing, and the parsing the compiler necessarily must do is not exposed in any kind of object tree, but instead reserialized to string.Format compatible syntax necessitating re-parsing at run-time). Also, like expression trees, this was really hacked into the language, so, e.g. you can't participate in other normal C# features like overload resolution the way you might expect, extension methods plain don't work, culture-sensitivity can be a gotcha: basically this works for immediately evaluated expression, but is tricky elsewhere.
- razor (not strictly C#) is hugely complex, and has a very impractical underlying model. Compared with e.g. JSX which is trivial is (ab)use creatively, and which uses mostly language-native constructs for control flow, razor makes it impossible to use even basic features like methods to extract bits of common code; lots of basic programming features are reimplemented differently. Instead of passing a lambda or whatever, you have to deal with vaguely equivalent yet needlessly different stuff like partials + tag helpers.
- optional parameters are kind of a mess (no way to enforce named args, no way to cleanly wrap optionals, restriction on compile-time constant, interaction with overloads can be suprising); tuples are too (names are dealt with differently than everything else in the language, no syntax for empty or 1-elem tuples, no way to interpret arg lists as tuples or vice-versa, no checks on nasty naming errors like swapping order); equality is a mess (how many kinds are there again?), lots of apis are disposable but should not be disposed but for others it's critical, no good way to compose disposables, huge ever expanding api without practical deprecation path is a pitfall for newbs, no partial type inference for generics, no unification of all the various func-and-action variations means billions of pointless overloads (and sometimes per-API ways around it); tuples and anonymous objects are sort of redundant, but not entirely; no good way of implementing equality/hashcode/comparability and yet easy way to detect misused non-equatable types.
I mean, I respect their choices here, and there's a tradeoff with lots of benefit too: they're really quite fast-moving, and I want those new features ;-). But it's not without costs; they definitely aren't "much more methodical" or anything like that.
I look at the feature list in the latest iteration of the language, and my thought is, "Y'know, you really should stop when you're done."
Java is not trying to "keep up." It is intentionally slow-moving and conservative (this design goal was set by James Gosling when Java was first created), and only adds features once they have been proven in other languages for a while.
On the other hand, what I consider a major mistake from Java side was ignoring value types and AOT compilation since it's inception.
Had Sun blessed such features since the beginning, and many use cases for C and C++ wouldn't be necessary.
As to baking variance into the runtime, I think this is just a bad idea, which is so far used only in C++ and .NET, two languages/runtimes with notoriously bad interop (it's not just Scala; Python and Clojure have a similarly bad time on the CLR, as would any language not specifically built for .NET's variance model). It is simply impossible to share a lot of library code and data across languages with different variance models once a particular one is baked into the platform. This is too high a price for a minor convenience.
Specialization for value types (which are invariant), is another matter, and, indeed, it is planned for Java. Perhaps some opt-in reification for variant types has its place, but not across the board. I am not aware of other platforms that followed in .NET's misplaced footsteps in that regard. Those that are known for good interop -- Java, JS and LLVM, don't have reified generics.
What's worse is that it's a mistake that cannot be unmade or resolved at the frontend language level. Even Java's big mistakes (like finalizers, how serialization is implemented, nullability and native monitors) are much more easily fixed.
There's a reason java had built-in value types from day 1, because it made sense even back then.
Frankly, I think both java and C# kind of got this wrong. There was an overreaction against the C/C++ of the day, and whereas the GC turned out brilliant, the idea that it's not even necessary to express the notion of references/pointers/values etc. was too much; and the idea of a single type system root (object) is similarly dubious, and then particularly the idea that that root type isn't the empty type. Object has semantics, and that was a mistake, because it contributes to the bloat. I'm totally happy with ignoring those features 99.9% of the time, but having them completely unavailable makes those 0.1% cases extremely expensive. (I mean, I think those things are slightly changing, but it's slow going).
AOT compilation not so much, given the NGEN constraints.
However they got it both wrong, considering CLU, Modula-3, Delphi and Eiffel are considered influencial languages on their design.
That was necessary for performance back then. User-defined value types weren't, and Java has done well without them.
> Object has semantics, and that was a mistake, because it contributes to the bloat.
I think most of the RAM bloat is due to the GC trading off extra RAM for speed rather than object headers, and I'm not sure trading off complexity for headers was right 25 years ago (JS is doing fine on the client without value types). What changed was the performance characteristics.
As to object semantics, it may be a fixable mistake. The goal is to get value types without today's object semantics while preserving a single class hierarchy at the same time. The Valhalla team thinks that's achievable.
All of them refer that having value types alongside GC had a relevant impact improving performance.
All systems designed before Java was a thing.
Or since you refer to JS, the paper about SELF's design.
Even Dylan was designed with AOT/JIT and value types support, which is relevant here given that its domain was being a systems language for the Newton. That politics killed it is another matter.
I don't know why there was no emphasis on AOT back then. I guess they started with interpreter/JIT, and then there just wasn't much demand for AOT until now.
QuickBasic supported value types and AOT to native code, and while Visual Basic used P-Code, version 6 introduced a proper AOT native compiler.
Modula-3 was also a big influence, at least accordingly to some papers.
There was surely demand for AOT, given that most commercial JVMs had it in some form or the other since 2000.
Even Sun actually supported it in Java Embedded variant for OEMs, probably grudgingly.
Common Lisp certainly has support for value types.
As for AOT, there may not have been sufficient demand from Sun/Oracle. I only joined relatively recently, but we generally do expensive things only if we believe they have a huge benefit or in huge demand, and we believe it can be long-lasting. The assumption is that any new feature will require maintenance for 20 years, taking away resources from other things. So if something is expensive, even if it's cool or some people could find it very useful -- we don't do it. The assumption is that the ecosystem is large enough that others can, and will.
I can check the respective manuals if you wish.
Yep, I did VB programming for a short while.
And please note that even though my focus is now elsewhere, Java is one of my favourite eco-systems.
As a peasant I just wished that Java 1.0 was more like Go, given the existing alternatives back then.
So it kind of stayed as a pet peeve of mine.
Same applies to .NET, just in a different way.
However due to the hardware architecture changes and new kids on the block, it is starting to be an issue.
I keep wishing to see them arrive, have watched all the JVM Language Summit, Devoxx and JavaONE talks about them.
Meanwhile I can already enjoy them elsewhere. :(
That's perfectly fine. We think that our priorities are right for the workloads Java is used for (e.g. people care more about a low-latency GC like ZGC, and deep low-overhead in-production profiling, like JFR, than about AOT).
Value types were pretty much obvious as necessary.
More so when one dives deep into how languages like CLU and Mesa/Cedar were designed.
Having a AOT support doesn't preclude having a JIT as well, like Common Lisp or Eiffel already had in 1995.
Gosling said that his goal was to have nothing you can somehow live without (I don't know how well early Java lived up that ideal, but that was the ideal). Hardware changes made user-defined value types absolutely necessary for workloads Java wants to target.
If I recall correctly it even depended on WINE.
Are there any plans to fix those?
C# i s one of the best dev experiences in any language/IDE
[1]: Maybe not C# programmers, but there are easier ways to do a single-language runtime.
Go's goroutines seem okay - but I don't like how much control they take away from the programmer. For example, last year I worked on calling-into a black-box C DLL from a Go program and we learned the C DLL had code that was actually simply terminating the thread inside of it (by design!) because the author of the C DLL assumed ownership of the thread. That caused a problem for us because Go's goroutines are scheduled by the Go runtime and it will never let you give-up ownership of a Go thread - and I couldn't see how I could use my own thread (e.g. getting a thread from a native OS call to keep it outside of Go's control) with goroutines. The project was almost DOA after we learned this, fortunately we convinced the author to always return instead of killing the thread. I'm not sure if anything's changed in Go since then that would have made things easier for us. But since then we haven't used Go for anything new. The only reason we used Go was because it gave us binaries that "just worked" for Windows, macOS and Linux without having to worry about Java, .NET and other dependencies - but I wasn't happy about the ~20-30MB-sized executable output.
Care to expand on this? Java is very careful to release new features.
Java has made a U-turn in adding streams and related functional features on top of a language that used to be strongly for OOP (actually defining the meaning of OOP for a generation of developers.)
These different paradigms together make for code that does not read the same no matter who writes it.
I love how Go code usually ends up being extremely similar, no matter who write is. (Actually, Kubernetes is a counter example for this: Go should have gone even further in forcing style.)
If you think that "all code reads the same" is detrimental to developers, you are conflating the idea of developer (problem solver) to that of coder (keyboard typist.)
So, by adding a feature which works extremely well with OO and enhances the language they have no soul? That doesn't make any sense. Javas soul is being a blue collar language. It leaves the experiments to other (JVM) languages and takes the parts which have been shown in the field to be useful for many cases.
Go on the other hand is a half-finished Java, produced for the sake of saying "We are Google, OF COURSE we have our own language".
One package is written in the "new" functional style, another is written in "old" object oriented style, other parts use classes for nothing more than name spaces to house static methods. The reality is it's already a mess.
But isn't that just an inevitable outcome of aging? The only way to never age is dying young.
And practical use of Java had stopped being textbook OOP (which means using classes to model the world and putting your logic where the data is) long before the streams API etc came along. Actually the shift away from textbook OOP integration between logic and data already started when the beans pattern drowned us in getters and setters, which already happened when Java generics were still the Pizza language sitting as a draft on Odersky's desk. I even suspect that lack of generics was a confounding factor in that shift, because when you look at a pre-generics codebase (which I happened to do just yesterday) you will find that the brave men and women back then spent an enormous amount of boilerplate just for hiding untyped collections behind typed facades which is a form of using OOP features (meant for modeling the world) for program structure, which is basically what post-textbook-OOP Java is all about.
A design philosophy. Some sort of measurable practice that influences design. This is not limited to Java. Design by committee has been destroying the maintainability of many languages.
Streams are inspired by some idioms of functional programming. But they are not functional, and they cannot be made to be functional, because it is impossible to evaluate one without causing a rather glaring side effect.
I want expert code to be concise and uncluttered. When everything reads like a novice wrote it, that's a problem.
Your comment is the problem with the Go community. I have seen a number of comments from Golangers that they want a "fun" language that helps them reminisce about the past. They also want to write a lot of senseless boilerplate because for them more typing is somehow about them reliving their past. And tracking down nil pointers... and writing containers for every concrete type.
The simple fact is software development has gotten more complex because business requirements have changed and Go does a poor job of addressing that with its limited feature set. The rest of the programming world has accepted that we need better tools whether it be toolchain stuff or language features. Hate on Java all you want but at least it, like most other non-Go languages, has realized the need for better tools in the toolbox.
Sure, and the "right tool for the right job" is still my mantra. Go is a very good at solving for an incredible amount of tasks in many problem spaces. Users will always want to bend tools to work in new places, and that's okay -- sometimes it isn't a fit.
Have some business requirements that make using Go a chore or a pain? Use a different language, or restructure the requirements.
That's such a cop out answer when our industry is basically doing fad-oriented engineering. It's great when you can greenfield build a project but when you're taking over a project the selection of language has usually been decided. Or you know the management team has decided for hirability reasons to use X. Or just legacy requirements don't match reality eventually.
This is why we need more expressive languages than Go. Requirements change over the course of a projects life and what made sense in year 1 rarely makes sense several years later.