It's not clear that running a bunch of code in the form of a copy constructor and potentially performing a bunch of heap allocations whenever someone returns a value, passes an argument, assigns a variable, or evaluates a temporary as part of an expression is a good thing.
> - Generics : The overall theme of forcing you to do things its way seems to a staple with java. Why do I need to use type-erased generics ? why shouldn't I get the choice to specify whether I need a new class generated for runtime efficiency or use the type-erased catch-all for size efficiency? there is no need to bake VM-level support for this, it can all be done at compile time (possibly with help of additional metadata files or special fields in the .class of the generic type).
There ends up being no difference, right? Your generic ArrayList is just an ArrayList of pointers to Object no matter what. You never had the option of declaring an ArrayList of structs.
This is not unique to Java and this difference is present even in a modern language like Rust. Primitives are copied by value, objects are copied by reference. If you want copy by value use Java records: https://www.baeldung.com/java-record-keyword
> Baking in choices about object representations : Like the fact that objects are always passed by reference, or that they are always allocated on the heap
Because the JVM does escape analysis and performs automatic stack allocation. The JVM wants to retain control here.
> Generics
Type erased generics were a deliberate design decision implemented for backward compatibility. .net had to introduce brand new collections while java programs could continue to work. To be honest, I felt this was stupid too. A clean break would have been better.
> Overall verbosity
Deliberate design decision. Though frankly, I personally prefer extends and implements. This is just done once at class declaration and allows for easy and no-nonsense grepping of sources from the CLI.
> Why aren't any constructors generated
Satisfied by records now which are meant as the placement for POJO's. Classes should now be used for services - for which you _don't_ want generated constructors.
> Horrible OOP excesses
Looks like you are ~10 years out of date on Java tech and community. Perhaps look at some of the modern java libraries ? Look at GraalVM ?
I prefer coding in Modern Java over Kotlin. Kotlin readability is poor.
You're confusing my copy-by-value-copy-vs-by-reference criticism with my type system criticism. The point you're replying to is a point about the type system. Rust has no notion of a unified type system, so it can do whatever it wants. (It's still better to not have too many exceptions and special cases in your type system, I don't know enough about Rust to know if this is the case there.)
Java announces at the start that all types share a common ancestor, an idea it got out of objective-C which got it out of smalltalk. If you're going to do that, you better go all-in and make sure that ALL types really do in fact share a common ancestor. Objective-C couldn't do it because it was wrapping C, but in a new language there is no excuse to make a rule then immediately listing several exceptions to it right off the bat. Scala and Kotlin are proof it can work, your primitives are compile-time objects that you can call methods on and do all the things you can do to objects, the compiler decides if your code can compile to JVM primitives (and therefore the method calls are static function calls) or if you had done something that requires boxing like Generics.
>Because the JVM does escape analysis and performs automatic stack allocation
There is no reason it can't do all those things and give the developer the ability to explicitly specify that they want this data on the stack, for all the data that can be allocated on the stack (e.g. not dynamically sized arrays). The object representation is a low level yet very important question that shouldn't be monopolized by the language.
There is also no reason to bake what I'm saying into the JVM, the compiler is there. It can "unwrap" the objects that you stack-allocate into primitives (recursively) and translate all the code that manipulates the objects into equivalent code that manipulates the underlying primitives. The JVM would be none the wiser, all it would see is primitives.
This is what I mean when I say that Java is an assembly language, it tries too hard to reflect the underlying VM, developer ergonomics and productivity be damned.
>Deliberate design decision.
I never said it's accidental, I said it's bad and ugly and horrible for developer productivity.
>This is just done once at class declaration
And is this a rare thing in java ?
>easy and no-nonsense grepping of sources from the CLI
Regex is far from a no-nonsense solution to anything, for one thing it's sensitive to spaces and newlines. You can't match "class foo <newline and several tabs later> implements Iinterface" for example unless your regex explicitly and verbosely account for it. Kotlin's regex won't be anymore difficult than the equivalent java one that handles the same edge cases, syntax easy for humans is syntax not too difficult for machines.
Here is an example :
"class [^:]+:(\s|.)*Iinterface"
This matches all classes implementing Iinterface, accounting for any whitespace and the fact that Iinterface might not be the only interface implemented. This is pretty mild by regex standards.
>Satisfied by records now which are meant as the placement for POJO's.
Being late matters. Generating things automatically isn't rocket surgery, it has been done by languages since forever. It isn't enough to wake up, you have to do it at morning.
>Classes should now be used for services
Keyword is "should". Good luck forcing it on a community after being late for 20 years.
>which you _don't_ want generated constructors.
So override them. If you already know that you don't want constructors and you're going to override them anyway, why should the language force everybody to conform to your choices. Generating obvious things should be the default, it's up to you, as a person who wants to override the defaults, to know that you should override the defaults and go override them.
>Looks like you are ~10 years out of date on Java tech and community
I'm right here in this day and age, and no the horrible excesses and design pattern fetishism never went anywhere in the vast majority of non-performance-critical code.
>Perhaps look at some of the modern java libraries
Does Android count ? because I have seen things there you wouldn't believe, 7-class deep stacktraces full of "abstract" and "Impl".
> GraalVM
It's hardly fair to claim that a compiler\VM codebase is typical code.
>Kotlin readability is poor.
This is what I call the "COBOL view" on programming language readbility. The language should be as verbose as possible and full of natural language words to imitate an informal document.
Needless to say, I'm not very keen on this view. It's misguided. Readability is precisely when the language gets out of your way. For this to happen, it's crucial that it's not verbose, if you want verbosity later you can add it with vebosely-named language abstractions. In a language that allows macros (which isn't kotlin), you can make "implements" a keyword, if you want.
The keypoint is that verbosity and extreme detail is not readability, not always. Even in natural language. Readability is what happens when the problem is described at the exact level of verbosity that makes its solution fit in one human brain. The language shouldn't claim to know this level in advance for all possible problems, it should be as austere and as minimal as superhumanely possible, it should get out of the way and let those describing the problem decide how it should be described, because they know better (about the problem) than any language designer.
In other words, you can make Kotlin verbose for problems that benefit from it, but you can't make Java not verbose for problems that benefit from it.
Modern Java (11+, and especially 17) has greatly improved the language. Sure, there's still type erasure and primitives, but it is adding a lot of features that matter like record types, more efficient GC, deprecation of older classes, and more fleshed out APIs.
I can see why you'd dislike Java. There are a lot of things that could be improved. There are times when it is the right language and times when it isn't.
My point in the GP comment is just that there fundamental design things that IDEs can't fix. Java is badly designed, IDEs can do damage control, but it can't undo what the mistakes of language designers.
You can omit them, and then the member is package private.
class A {
has $!bar; #private
has $.baz; #public (with autogenerated accessors)
method !foo {} #private
method foz {} #public
}In Raku they can. But one can also introduce new ones. More generally, it's nice Raku makes it easy for a dev to write a module that lets other devs write code and get nice output like this:
use Physics::Measure :ALL;
# Define a distance and a time
d = 42m; say d; # 42 m (Length)
t = 10s; say t; # 10 s (Time)
# Calculate speed and acceleration
u = d / t; say u; # 4.2 m/s (Speed)
a = u / t; say a; # 0.42 m/s^2 (Acceleration)
# As a special treat it allows working with measurement errors:
d = 10m ± 1;
t = 8s ± 2;
say d / t # 1.25m/s ±0.4
(With thanks to SteveP for the module and niner for the above example code.)
I do, because I'm a Java hater. Here are the reason I hate it for
- Baking the difference between primitives and objects into the language itself : an ugly mistake with far reaching consequences, made by a language designed in 1995 while another designed in 1980 (smalltalk), in 1991 (Python) and 1995 (Ruby) all didn't fall for it.
The difference is an irrelevant VM-level optimization detail, there is no reason to uglify the human-level language with it. Once the initial mistake has been made, the correct response was NOT to make the even uglier hack of wrapper classes, but to make the primitives objects in the newer releases of the language, this won't break old code, as valid uses of objects are a superset of valid uses of primitives, except perhaps that objects need to be allocated explictely with "new", but this can be a special case for primtives (i.e. "int is a special kind of object that you don't need to allocate explicitly"). The compiler can figure out whether it needs to be represented as objects or as primitives, you can leave hooks and knobs for people to tell the compiler they need to the primitives to be represented as primitves, but it shouldn't be mandatory.
- Baking in choices about object representations : Like the fact that objects are always passed by reference, or that they are always allocated on the heap. Why the "always" part ? why not give developers the choice between pass-by-value and pass-by-reference like C# does ? why not give developers the choice to allocate on the stack (and complain as loud as you want when they want to do something unsafe with it, like escaping from methods), which, unfortunately, even C# doesn't ?
Everytime you see something like "foo deepCopy()" that's a failure of the language, forcing you to explicitely pay attention to the fact that foo objects need to be copied deeply everytime they are copied, instead of just once when you define the object by marking it as a "struct" or whatever word to signify that object has value semantics, and then deep copy is just assignment or passing as a parameter. Why make it the default to be inefficient with the heap when it's very easy to give developers the choice to be efficient in situations where it's always safe ?
- No operator overloading : I get the hate, it's a powerful tool. But it's misguided to ban it, operators should not be special, languages like Haskell and Raku go even further and allow you to define new operators entirely and control their predence and other things. You don't need to go that far, why can't objects use the already built-in symbols the language support ? because it might be confusing ? anything can be confusing, you can write assembly in any programming language, and it will be even worse than assembly because of the more powerful and obscure abstractions.
- Generics : The overall theme of forcing you to do things its way seems to a staple with java. Why do I need to use type-erased generics ? why shouldn't I get the choice to specify whether I need a new class generated for runtime efficiency or use the type-erased catch-all for size efficiency? there is no need to bake VM-level support for this, it can all be done at compile time (possibly with help of additional metadata files or special fields in the .class of the generic type).
- Overall verbosity : Why "extends" and "implements" ? do you really need to know whether you're inheriting a class or an interface ? and can't those be lighter symbols like "<" and ":" perhaps ? why is "private/public/protected" a must in front of every method and field ? most people align fields and methods by their visibility, C++'s way is that you declare "public:" and then everything declared below that is public. In the worst case you can always recover Java's way by "public : <method> ; private : <method> ; public : <method>" and so on, but it's nice to at least have the choice of not repeating yourself.
Why aren't any constructors generated ? there are at least 2 very obvious ones : the empty one, and the one that assigns all the non-defaulted fields (and can take optional arguments to override the default fields). Why aren't generated getters and setters available with a small and light request, like C#'s "get ; set ;" ? Java's design is just full of things like this. It feels like a weird sort of disrespect for your time, "yeah you must write those routine 25 lines of code all by yourself, you have anything better to do?", how about actually writing my application instead of pleasing your language with weird and unnecessary incantations ? It's like a modern COBOL.
- Horrible OOP excesses : Not really the language's fault (except that it encourages verbosity and loves it) and already mentioned, but worth mentioning again.
Overall, I treat java as assembly. I write kotlin in my spare time, and whenever I'm confused about the semantics of some construct I make intellij show the bytecode then hit "decompile" to see a Java rendition of the code, the exact semantics will be obvious but verbose. A language that took this literally is Xtend, a high-level augmented java which transpiles to java and is a strict superset of it, but with option that the Xtend compiler figures out all the verbosity for you. Groovy also takes the "Superset and Augment" approach but doesn't transpile. And off course Kotlin is very good with it's interoperability, every JVM language is but Kotlin's mixture of being close to Java semantics (unlike say, Scala or Clojure) and Intellij excellent support for mixed projects makes it at least somewhat special.
I like the JVM and it's cutting edge research and performance, and these days the Java standard writers seem to show signs of finally waking up to reality after years of being behind every mainstream language, and they regularly augment and modernize the language. But you can't undo 20 years or so of bad design, not easily and not painlessly.
>Indeed, tooling is the new syntax.
Very much agreed, long long gone are the days when a compiler or an interpeter is the only thing expected out of a language. But it's not a panacea to treat any bad design, at best it's just a band-aid for bad designs that makes them barely berable. The language has to be designed from the start with the knowledge of "this is going to run in an IDE" baked in to make full use of the full range of fantastic things an IDE can do.
[1] https://www.eclipse.org/xtend/