> I disagree here. Having GC, VM, streams, big stdlib, makes is quite highlevel.
I used to think that, too. I probably wont't convince you otherwise, and it really doesn't matter how you or I categorize the language, but I think a solid argument can be made that Java's abstraction power is almost zero, especially if you consider versions older than two or three years (before records, switch expressions, sealed classes, etc). I also think that the ability to differentiate between boxed and unboxed primitives, no concept of immutability/const, primitive synchronization tools like mutexes and raw threads, etc, make a compelling case that Java is not well-suited for thinking at a high level of abstraction.
Think about how much code it required in Java to create a value type with four fields before records. You needed to list all four fields and their types in the class body, then you need to list all four fields and their types in the constructor signature, then you need to write `this.foo = foo` four times in the constructor body. Then, depending on your conventions and preferences on mutability, etc, you'll need to write getters and/or setters for the four fields. Then you need to write a custom `equals()` implementation. Then you need to write a custom `hashCode()` implementation. Then you need to write a custom `toString()` implementation.
I hope you don't have to update that class, either, because forgetting to change your `equals`, `hashCode`, and `toString` will cause bugs.
There's basically no universe where you can convince me that this shouldn't be considered low-level programming.
> To keep a language small is a good thing: less to remember, easier to join the team. Go, Elm, Reason/ReScript, LISPs all go that route.
I agree! Small/simple is great. But look at how expressive Reason/ReScript/OCaml is/are compared to Java. Same with LISP. They aren't huge languages with endless features being added on all the time, but they allow for much more high-level programming than Java, IMO.
> Java misses some things badly. Like being able to have a reference to a method (Jodd has a library fix for this). Or like sum types and pattern matching.
To be fair, though, this is not what Java was designed for. Java was initially an object-oriented language. Sum types and pattern matching are not OO. Object-orientation was supposed to be about black-boxes sending "messages" to each other while maintaining their own internal state and invariants. In "true" OOP, you wouldn't have a sum type, because you'd have an object that would exhibit different behavior depending on the implementation.
Granted, we're moving away from hardcore OOP as an industry (thank goodness). But, I'd argue that the "problem" isn't Java's lack of sum types and pattern matching, but rather that we're trying to make Java into something it isn't. We should just use a different tool. I'm not in my garage trying to attach a weight to the end of my screwdriver to make it better at driving nails- I'm going to my toolbox to grab a hammer, instead.
Agreed with many points. So Java is then somewhat in the middle.
OTOH lets consider Rust. It is in my book a low-level lang, close to the metal (hence Rust?). It has a muuuuuuch better feature set compared to Java (IMHO). But it is geared at low-level, so no VM and certainly no GC out-of-the-box... In your def Rust'd be a high level lang: which is cool. I like your def :) But I still def'd high level slightly different: more in terms of the ability program close to the machine, or more in abstractions.
> Sum types and pattern matching are not OO.
Traditionally not often found in OO, but otherwise verrry much compatible with OO.
> In "true" OOP, you wouldn't have a sum type, because you'd have an object that would exhibit different behavior depending on the implementation.
I think this is more about tradition than "trueness". I cannot return an Either<Error, Result> from Java. That sucks. Many have used Exceptions to fix it, but that suck even more. I'd say OO is compatible with sum types.
> But, I'd argue that the "problem" isn't Java's lack of sum types and pattern matching, but rather that we're trying to make Java into something it isn't.
This always happens. And some langs are better suited for that than others. I work on a Java codebase currently and welcome those features, and actively consider moving the whole show over to Kotlin. Kotlin to me is like a typed Ruby. And in Ruby many libs are in C (in Kotlin then many libs'd be in Java).
I think OO and FP bite eachother. You cannot have both. See Scala. It becomes way too big as a language, and lack idiomatic ways of doing things. But one can have a lot of FP in an otherwise OO lang (see Kotlin for instance).
> OTOH lets consider Rust. It is in my book a low-level lang, close to the metal (hence Rust?). It has a muuuuuuch better feature set compared to Java (IMHO). But it is geared at low-level, so no VM and certainly no GC out-of-the-box... In your def Rust'd be a high level lang: which is cool. I like your def :) But I still def'd high level slightly different: more in terms of the ability program close to the machine, or more in abstractions.
I'm not a super clever person, but I once made a quip that I was pretty proud of, and I've repeated it online a few times:
"Rust is the highest level low-level language I've ever used. Java is the lowest level high-level language I've ever used."
Of course, the hardest part of all of these discussions is agreeing on what the words we're using actually mean. So what does "high level" and "low level" mean when it comes to programming languages? Are they mutually exclusive, or can a language be both? Is there such a thing as a "middle level"?
I don't have a great objective definition. Basically, I see "low level" approximately meaning "I have to think a lot about computery shit" and "high level" as "My code mostly looks like domain logic". There's a lot of wiggle room in there, for sure.
But I'm curious to challenge you more on what you mean by "close to the metal." Is being close to the metal somehow about abstraction-ability of the language, or is it a euphemism for some languages just being inefficient with computing resources? And, specifically, the things that make Rust closer to the metal than Java. I think the "obvious" answer is that Java runs in a virtual machine and has garbage collection, whereas Rust has neither of those. But I'm going to push back on those "obvious" high-level features.
First of all, Java-the-language has no idea that it's running in a virtual machine. I could, hypothetically, write a compiler for Rust that spits out JVM bytecode- would that make it a high-level language? Probably not.
As for garbage collection, I'd agree that compared to manually allocating and de-allocating memory space, garbage collection certainly allows us to think in a higher level of abstraction by letting us ignore details about how and when our data come to exist in our program. But (safe) Rust's approach to memory allocation is pretty far from manually allocating and de-allocating blocks of memory from the OS (which is basically a VM, itself, isn't it?). Rust largely allows me to ignore how much memory I might need for a String or a vector of data.
Now, it would be crazy for me to claim that Rust's memory model is as high-level as something with a garbage collector. After all, in Rust we have to think about borrows, Sized vs. unsized types, and sometimes have to actively think about lifetimes.
But, I will claim that Rust's memory management is still a big step up the ladder of abstraction from C. So, if being "close to the metal" is about needing to think less about nitty-gritty computer stuff, then Rust isn't as close to the metal as our gut instinct might say it is.
On the other hand, in Java, I still need to think about specific computery stuff when choosing between boxed and unboxed primitive types. I need to think about bits and bytes when choosing Short vs Int vs Long, rather than having a default Integer type that can be arbitrarily big or small. I need to think about mutexes and threads and thread-pools for concurrency/parallelization. I need to worry about stack overflows. That's all true of Rust, too, of course, but my point is that both of them require putting a lot of thought into non-domain concepts while programming.
Java did recently get records and sum types, so its abstraction ability has gone up substantially from where it was just a few years ago.
Rust has async/await for concurrency that can even be used in single-threaded contexts. Java doesn't even have that yet.
Rust has type classes. Java does not.
Rust has easy-to-implement newtypes. Java does not.
Rust has (im)mutability as a language concept. Java does not.
Rust has data copying as a language concept that actually works. Java has Clone.
Rust has hygienic macros that can be used to extend the language, create DSLs, and reduce boilerplate. Java has annotations that can be used to reduce boilerplate- mostly with a runtime cost and runtime errors.
So, which language is more capable of higher levels of abstraction? Honestly, it's probably Rust. Which language requires you to think more about memory stuff? Rust- but I think it's less of a lead over Java than most people would guess.
Which is closer to the metal? I don't know. Rust runs faster.
> Traditionally not often found in OO, but otherwise verrry much compatible with OO.
> I think this is more about tradition than "trueness". [snip] I'd say OO is compatible with sum types.
We'll probably have to agree to disagree. Of course sum types can exist in a language that touts itself as OO, but using them extensively is just not OO. It's literally inside-out from OO. If you look at a language like SmallTalk, even True and False are objects, and there are no if-statements. Rather, True and False are both sub-types of Boolean, and Boolean requires the methods ifTrue and ifFalse. True implements ifTrue to perform any action that was sent as a parameter, and implements ifFalse as a no-op. You can imagine False's implementations. So, some object sends you a Boolean and you call the ifTrue method of the Boolean with an action to be performed if the Boolean feels like it (it feels like it when it's a True :p). In true/extreme/hardcore/pure OOP, you wouldn't even have or use if-statements when implementing logic- it's polymorphism all the way down.
Is that useful or practical? I don't think so. But that's why I claim that sum types are not OO. I also claim that if-statements aren't really OO. And boolean is a sum-type.
> I cannot return an Either<Error, Result> from Java. That sucks. Many have used Exceptions to fix it, but that suck even more.
Yes you can. Java now has sum-types anyway, but you could always implement a generic class that could be in either one of two states with whatever methods you need to check its state and extract the data. It's awkward and janky, but this is Java- what isn't awkward and janky?
> I think OO and FP bite eachother. You cannot have both. See Scala. It becomes way too big as a language, and lack idiomatic ways of doing things. But one can have a lot of FP in an otherwise OO lang (see Kotlin for instance).
I agree with the first premise, but I disagree that Kotlin has successfully added FP stuff to an OO language. I think that Scala has done a much better job of being FP and OO, actually.
I used to think that, too. I probably wont't convince you otherwise, and it really doesn't matter how you or I categorize the language, but I think a solid argument can be made that Java's abstraction power is almost zero, especially if you consider versions older than two or three years (before records, switch expressions, sealed classes, etc). I also think that the ability to differentiate between boxed and unboxed primitives, no concept of immutability/const, primitive synchronization tools like mutexes and raw threads, etc, make a compelling case that Java is not well-suited for thinking at a high level of abstraction.
Think about how much code it required in Java to create a value type with four fields before records. You needed to list all four fields and their types in the class body, then you need to list all four fields and their types in the constructor signature, then you need to write `this.foo = foo` four times in the constructor body. Then, depending on your conventions and preferences on mutability, etc, you'll need to write getters and/or setters for the four fields. Then you need to write a custom `equals()` implementation. Then you need to write a custom `hashCode()` implementation. Then you need to write a custom `toString()` implementation.
I hope you don't have to update that class, either, because forgetting to change your `equals`, `hashCode`, and `toString` will cause bugs.
There's basically no universe where you can convince me that this shouldn't be considered low-level programming.
> To keep a language small is a good thing: less to remember, easier to join the team. Go, Elm, Reason/ReScript, LISPs all go that route.
I agree! Small/simple is great. But look at how expressive Reason/ReScript/OCaml is/are compared to Java. Same with LISP. They aren't huge languages with endless features being added on all the time, but they allow for much more high-level programming than Java, IMO.
> Java misses some things badly. Like being able to have a reference to a method (Jodd has a library fix for this). Or like sum types and pattern matching.
To be fair, though, this is not what Java was designed for. Java was initially an object-oriented language. Sum types and pattern matching are not OO. Object-orientation was supposed to be about black-boxes sending "messages" to each other while maintaining their own internal state and invariants. In "true" OOP, you wouldn't have a sum type, because you'd have an object that would exhibit different behavior depending on the implementation.
Granted, we're moving away from hardcore OOP as an industry (thank goodness). But, I'd argue that the "problem" isn't Java's lack of sum types and pattern matching, but rather that we're trying to make Java into something it isn't. We should just use a different tool. I'm not in my garage trying to attach a weight to the end of my screwdriver to make it better at driving nails- I'm going to my toolbox to grab a hammer, instead.