Me too. Poorly worded title; suggests the opposite. I thought it might be something different so was intrigued and then disappointed to see the normal angle.
Yet, Java-codebases look the same now as they did then. The program structure is still "externalized" to a container because of a lack of abstractions. It's still exactly the same.
I really don’t get what you mean. Java is not the most expressive language, but it is eons ahead of the expressivity of C or Go and yet I don’t believe it would be fair to claim that those languages also externalize their structure?
What I mean is that a Java program is almost always sitting in a Spring container with most important stuff happening as "decorated magic"; this has been the case since even before 1.6. To say that it is somehow better now than it was, is therefore not worth much. Sure, I'm sure you guys'll put a map or a stream in there somewhere but it's ... still the same thing. There's no real evolution at all.
The springification is what I mean by it being externalized and you generally don't see that in C, no. The pre-processor might be sort of the same thing but you generally don't bootstrap the entire thing using it.
Well, Spring is mainly for backend systems that have a very specific functionality that is best handled by a generic solution. In my experience this is in line with how most other web frameworks operate.
But this is absolutely domain-specific, Java the language is indifferent to exposing a library that will handle incoming request and dispatch them based on some library logic, or creating a “traditional” program for the litany of other domains where java is used st.
Spring isn't a web framework, it's gaffer tape for a language that just isn't good enough without it; that's why it's [spring] still around and its use is so prevalent. It's not at all mainly for very specific functionality in the sense that your post sort of implies [for web, and databasy things I presume]; in fact: it's just for wiring and for applying/ attaching things to "abstractions". As in: please decorate these sets of things with this behaviour.
If you'd chosen languages that permit abstractions in a real and usable sense, then you would not need this. In fact: with _one_ exception, I've never had a container in any Haskell, Scala or F# project ever. Do you think we ever needed one?
And this is my point: those other languages have seen _tons_ of development in the "abstraction arena" with (internal) DSL:s and super powerful frameworks for making powerful DSL:s. Java has had none of this because these concerns har all (poorly) handled at the container level.
The tacked on "functional stuff" is for sure cool coming from a Java backround, but they're pretty much useless.
Spot-on analysis. I work on a team (that strongly prefers using Kotlin) in the CI-CD world. Despite web dev only being a minor part of our work, Spring just attaches itself to everything like a disease. I feel like it has gotten to the point where most Java developers that interview know Spring and not Java. Dependencies themselves also become attached to Spring (our example: bringing in Kafka -- super painful without Spring and hardly any examples or docs.)
This "containerizing" is very unique to Java -- I've never seen anything like it in the other various languages I've used. I won't even mention the total disaster of JavaEE containers.
Yes, it is terrible and it's the main reason I'll never be doing any Java, ever again. I did 10 years up until Java 6 (or so) and then jumped ship in favour of Scala (at that point.) Now, Scala for sure isn't perfect and Java isn't terrible, but mainstream Java really is.
My point, though, is not to throw up all over Java but rather to quibble with the statement that Java has somehow evolved since then. It really hasn't!
It’s still heavily object orientated. My preferred approach to applications these days is immutable core / imperative shell and Java makes that awkward.
Basically you have business domain objects that have value semantics and are immutable. You have functions that manipulate these objects. This makes the business domain unit-testable. Then on the edge of the application you have imperative code that manipulates databases, serves requests, etc. It’s like Haskell but as a design pattern rather than a language.
In the JVM world, Scala and Clojure communities are adopting this approach.
The sibling comment explains it well on a conceptual basis. I can maybe add a few notes since I'm in the process of gradually moving code to this architecture (for the lack of a better word).
It is essentially straight forward to do. You look at a bunch of code in a module and determine which parts do these kinds of things:
- data integrity, validation
- parsing, transformation between formats
- domain logic, decision making
- algorithms and data structures
This is your functional core. Note it doesn't matter too much if you have local mutations as long as they don't leak out. What you want to achieve is to have an interface where you can ask questions and you get the same answer for a particular question. This is certainly more productive if your language affords you with functional constructs, but its not strictly necessary to get the benefits.
You code these parts in a way that they are data in, data out essentially. This leads to super easy and reliable unit/example tests and generative tests. And this part becomes very simple and easy to reason about.
Secondly you have the part of your code that actually _does_ stuff, affecting your environment in some way.
- basically any kind of I/O
- cross cutting concerns between other side effecting modules and services (think DI and such)
- error handling, error reporting and logging
- caching, throttling, filtering etc.
That's your imperative shell (I just call them modules). This part you write in an imperative/procedural style and you call the functional core parts from here, never the other way around. You coordinate effects so to speak.
It is clear that you test these with integration tests, where you need to setup specific program state (or use mocks, but I try to avoid that) in order to find different code paths. This is harder to test properly but at the same time you also get to see everything that actually happens in condensed form.
The degree to which you extract "pure" logic from your imperative shell is actually quite flexible. You can go as far as defining purely functional FSMs to extract much of the control for example. I think it really depends on code size and complexity as well as performance implications. The cool thing is that you can be quite pragmatic and iterative about it.
Big fan of that approach as well, but there's no reason why you can't do it in Java. Records and streams are your friends. It's actually rather straightforward to start from a neat, functional pipeline and then do whatever at the edge.
Of course you can't have the pure/impure distinction enforced, but nothing that isn't a purely functional language is going to give you that.
The only really dumb limitation is that you can't have freestanding functions, but that's aesthetics more than substance.
The latest additions to the standard library generally follow that approach: java.time, java.nio etc. But of course the older API are still fully mutable OO. A new collections API, immutable by default, is still needed and that's not happening any time soon.
I don't know. To me it feels like Java is a bit of a patchwork, with too many features added on top of it that weren't accounted for in the original design of Java. Generics are probably the oldest example, but there are plenty of others.
I agree. It needs more standardized tooling and libraries. But as a language it does not need anything more right now. Not even pattern matching. But some small touches here and there could be good.
Groovy and Scala have nowhere near the potential of Kotlin.
I think Kotlin actually has the potential to dethrone Java on the JVM, it is that good.
Kotlin is not much different from Java, conceptually. It is basically Java 2.0. Where Goovy does away with compile time typing, and Scala bolts a Haskell onto Java; Kotlin is "just a better Java".
Probably a good way to characterize Kotlin. Scala has a lot of interesting features but also a lot of complexity. And not all of those features are necessarily very good. Arguably, Kotlin was born out of frustration with that as Jetbrains at the time rejected the notion of using Scala internally and instead started the long process of developing their own language. It was mainly intended to be a better Java but also to avoid some of the complexity of Scala.
Groovy's lack of compile time safety is like throwing out the baby with the bathwater. It's just not worth it for me. Kotlin gets you most of the language features but without doing that.
> Scala has a lot of interesting features but also a lot of complexity
I really don’t get it - I am fairly certain that feature wise Kotlin is actually a bigger, more complex language with many exceptions/edge cases.
Scala is a really elegant language in that many of its features are solved by a singular base, for example implicits. And frankly, other than that it is a very easy language - programs written in it can be complex due to its high expressivity.
Looks like Flix is in a galaxy of languages implementing Datalog, a variant of (or somehow related to) Prolog. I guess Flix is Turing-complete while Datalog is not?
Too bad you need external tools anda lot of boilerplate that you must remember to enforce to achieve this, because it's an after thought and not a language feature.
And there's 7-8 different annotations to mark nullability, all slightly different in some detail.
Sure, language-support would be better indeed, but in practice the often encountered annotations are supported by every tool, and the only relevant setting is whether everything is nullable by default or nothing is.
It's still not enforced by the language itself, so nothing stops a third party library (that you have to integrate) from not using the annotations, and then the unknown nullability exerts a domino effect on your own code, "infecting" it with uncertainty.
TBF that is true for the Java-Kotlin boundary as well. You can use values coming from Java (like results from Android platform calls) as non-nullable if you wish, and it will blow up at runtime. The linter will catch those cases, but it's definitely less than ideal.
Bytecode analysis is pretty trivial. I'm not sure if modern tools do it, but figuring out whether that specific bytecode accepts null values or throws NPE with it is not hard (unless bytecode is available in runtime-only and compile-time dependencies contain only interfaces, but that model seems outdated).
Looking at a method/property signature it should be explicitly and unambiguously clear whether or not null is an acceptable value - but in java there is no way to enforce this.
The issue isn't so much whether it is null, but whether it can be null. Because if it can be null, you have to check whether it is null before you can actually do anything with it. That adds a lot of null-checking boilerplate to your code.
If instead the compiler can guarantee that a value will never be null, then you don't have to check, leading to cleaner code. Scala does this with the Option type, Typescript by forcing you to declare a type as `string|null` if it can be null. These sort of things make your code safer and cleaner.
I would argue that more concise syntax is actually why Scala became a niche and didn't get wider adoption and why some some examples of advanced Kotlin are really off putting. At certain point more concise syntax goes against readability.
... so preoccupied with whether they could, they didn’t stop to think if they should.
Tangent: I find the choice of keywords val vs. var in Kotlin a bit unfortunate. The way we read text (or rather skim) is a lot of the times by the shapes of entire words. Val and var are a bit too similar in this regard and I think let vs. var in other languages is much nicer.
On the other hand, when skimming some code, does it matter on the first pass whether a given variable is mutable? Probably not if we're just trying to get an idea of how it works; later on a closer read we can look closer if it matters.
I find this super-important. If a variable if mutable then I need to look closely through the whole of the rest of the scope to see if/where it is mutated. If it's not then I can often mostly forget about it. Thus marking variables as immutable where possible dramatically reduces the mental overhead of reading a function.
What I've found helpful is to configure IDE to use a separate formatting for mutable variables. You don't even need `var`/`val` for that, just smart enough IDE. Something like single underline for those variables. This way they're obvious and you'll know it when reading the code.
I mostly use JavaScript (which uses `const` and `let`) and Rust (which uses `let` and `let mut`), so I've find them quite easy to tell apart just based on the syntax :)
This is the reasoning behind the "const all the things" in C and similar languages, which doesn't have distinct keywords but where you can use "const" to make things immutable.
If a variable is const-declared, I react in exactly the same way and know that I don't have to look for further uses, which I find very valuable.
I even do stuff like this, which might be extreme but I find it coherent and nice:
The above const-declares the local pointer p, so that it can never be intentionally clobbered inside the function, which would leak the memory. Since it's the pointer itself that is constant, not the data being pointed at, there is no problem in modifying that memory, and the return value is not const-declared in any way.
If I'm not mistaken, that's how Swift (which is syntactically most similar to Kotlin as far as I can tell) has it.
But "let" is already a thing in Kotlin (a scope function in the standard library).
In my experience, in practice "var" are an order of magnitude more rare than "val", so I'd be perfectly fine with "val" vs. "mutable". I wouldn't mind the verbosity - it would gently discourage the use of mutability, while making it very apparent wherever it's actually involved.
Yes, it's "let" in Swift and in some (semi)functional programming languages too.
> in practice "var" are an order of magnitude more rare than "val"
Or rather, it should be that way. Agreed, and I think because the world has been moving more and more towards functional style over the past decades, I'd predict that the next big language that will make it to the mainstream will likely take the approach "immutability by default".
On Android - my platform - 99% of var usage is just for the sake of dependency injection, which is an inevitable concession because basic building blocks of an app (like Activities, Fragments) are instantiated by the SDK, not your code, so neither your code nor the libraries it relies on have control over it and thus the DI has to happen "on the fly", by mutating fields as soon as it becomes possible. It leaves a bit of a bad taste still, but it's very much a fixed convention and therefore kind of... compartmentalized, I guess. And harmless, because I don't expect Dagger 2 to suddenly misbehave and forget to set a field.
const is already a keyword - it denotes compile-time constants (basically primitive types). val is constant in runtime, but it can still be computed on the fly; just assigned only once.
mut is a nice proposition, I'd certainly consider it if I were to design Kotlin
Kotlin's "const" corresponds directly to Java's "const", so my guess is that they don't want to burn bridges with Java where there's no substantial benefit.
The current language team doesn’t even want to hear about properties and frankly I can understand their point. Properties while heavily used, are a misfeature.
I would rather see the ecosystem adapt records. Or once Valhalla happens, one can probably have a near zero-overhead Property<T> field for properties inside a record, properly supporting everything required by Java Beans.
Not quite true, one pedantic point of mine is that it may be a concrete class if that was marked as T’s superclass, the other is less nitpicky, generic information is actually available at the component itself, so plenty of tools can use it in certain circumstances.
I can echo the Author's sentiment. I've been working with Java and lately Kotlin for the past 25 years. The last five of which almost exclusively with Kotlin.
Switching back to Java feels increasingly awkward at this point. I can do it, but there are no use-cases where I would prefer Java over Kotlin. I certainly don't enjoy it.
Sure, they've added features to the language and they help but it's just not even close to the language ergonomics that Kotlin has.
The val vs. var thing is super relevant because this is actually something that is more cultural than language related. As the author points out, you could just litter your Java code with final and it would be similarly safe code. But the reality is that very few people do that. Java makes you do the wrong/less safe thing by default. Kotlin has this opposite culture where you feel slightly guilty/dirty every time you even make something a var. Having a difference between mutable and immutable lists and maps is likewise something that Kotlin inverts. In Java you could always make lists and maps immutable but they'd be immutable by default. With Kotlin it's the reverse: everything is immutable unless you make it mutable.
It's this safe by default attitude that pervades Kotlin that just shows up in a lot of places. Nullability as part of the type system removes an entire category of bugs. The only time I still see NPEs, it usually involves some Java library that I'm using.
Considering how easy it is to switch from Java to Kotlin, it's not surprising that you see essentially all the major Java frameworks come with excellent Kotlin support. I use Spring Boot with Kotlin. It's quite obvious to me that using spring without Kotlin is a bad move at this point. There's so much good stuff you miss out on that is added to Spring via extension functions. E.g. having co-routines sit on top of webflux, makes the whole business of using asynchronous IO with Spring a lot less painful. Without that, I'd probably still be doing synchronous IO.
Kotlin also discourages inheritance. It's opt-in, not opt-out, because classes are final by default: you need to explicitly mark them as "open" if you want to extend them. The same with functions if you want to override.
What I miss in Java is actually more the nullability, as the author points out. I prefer the Swift way of doing that. To me, it feels cleaner than the Kotlin way, despite being extremely similar. I think it boils down to var/let vs var/val.
As a C# Developer who occasionally uses Java I understand the sentiment. Java just feels dated. You have to do a lot by hand which should be done by the language.
And new features like streams are just a bad riff of .NET Linq, which is much better in ergonomics.
Finally, Generics are a big turn off, who ever thought that the Java implementation is a good idea was delusional.
Can you say more about this? My first thought is that you're talking about the various data types in Clojure being implemented with Java interfaces. But my understanding is (I'm not a Java developer) that interfaces existed pre generics, just without static type checking support, and thus Clojure could have been developed with a pre-generics Java version just fine?
IMHO, there's a more fundamental concept that everybody seems to be missing, which is that general purpose programming languages somehow fail at general purpose queries across trival, in-memory data structures.
A random example I like to bring up is: Can your LINQ-equivalent efficiently and idiomatically calculate both the "min" and "max" of a list without enumerating it twice?
Imagine you're writing some abstract library that can take any IEnumerable as the input(s). Will it scan a 1TB database table twice because the idiomatic coding style is:
var min = somelist.min();
var max = somelist.max();
This is such as basic, trivial example, yet C#, Java, Kotlin, etc... languages just fall flat on their face. Meanwhile, in SQL, a 48-year-old query language:
SELECT min(...), max(...) FROM sometable
But this is just scratching the surface. Ideally, all LINQ-style data inputs should be treated as something like a B-Tree index. Something that can be "walked" (not just enumerated!) efficiently using prefixes, ranges, and similar conditionals. Multiple queries should be able share such as "walk" to minimise the cost of streaming data in from memory... or wherever. Data should be partitioned across CPU cores just as efficiently, and the query language should make this natural.
I'm yet to see a language that scratches this itch.
Can you honestly tell me any usage of reified generics outside of perhaps ArrayList<int> possibly having another more performant implementation?
The amount of time I’ve seen it complained about, yet in practice I am very seldom hit by that (just a note, Haskell also erases every type yet it doesn’t bother anyone there).
The post gives an example - overloading a method based on the type parameter (e.g separate overloads for Option<Integer> and Option<Double>). This is why Java's generic erasure is more of a problem than Haskell's type erasure: Java erases generic parameters at compile time, whereas Haskell retains type information for long enough to use it when linking modules.
The devil is in the details: the capitalize example will badly fail on an empty string right? (substring will throw a StringIndexOutOfBoundsException).
So we guarantee you you'll get a String back. And hear this: we also guarantee you, 100%, it's going to be non null. And we guarantee you that the reference to that non null String is going to be immutable.
Oh but wait: maybe you'll get a RuntimeException instead...
Can just return "" in that case? I think you are focusing on the wrong details of an _example_. The details here is being able to extend and function chaining; not the algorithm of capitalize().
Oh I didn't explain myself correctly. I've got nothing against the example in the blog post. What I mean is that no matter if Java or Kotlin makes guarantees about null safety, invariability and the type of the object returned, in the end you still have to deal with the glorified GOTO statements that exceptions are.
They are the same thing as returning a sum type, just your goto hides in an if statement in that case you explicitly have to write out one way or another.
I like Java but being able to tell at compile time that don't allow null on something no matter what, gives you a lot of advantage (and some disadvantage if you design wrong). Exceptions are not necessary in this case as null safety can not be isolated (unless you choose it). It is either everywhere or nowhere or you force an arbitrary line by telling something not-nullable when it can be (not a good idea).
A good example is the state Dart is in right now. Almost everything has been transformed to null-safety including most of the community packages and it works really nicely.
"finally" in Java and afaik "val" in Kotlin because of it are a bit of a sham: They work fine for primitives, but for reference types only apply to the object identity. You can still mutate the pointed-to object willy-nilly.
The way this is solved in modern code is that you very rarely have mutable objects. So basically you can expect all objects to be immutable (excluding obvious exceptions like builder objects).
Some frameworks like JPA are not up-to-date with this methodology, sadly. But Java direction with immutable records is obvious and they'll adapt.
It is not like this in Java either if the components are all immutable as well.
It will be shallowly immutable, but applying it recursively will make it totally immutable. The reason I say that is that in practice it is not that different than FP languages, they just differ in the ratio of FP data structures.
const in C++ is more like Readonly from TypeScript - you cannot modify state of an object, but it still might be possible through other reference/pointer - you have to check source of that value to be sure
I’m not sure this is the reason, but OOP is supposed to handle encapsulation of state. For example lazy initialization of an object can be done by proper encapsulation and interior mutability, while the outside view can be completely immutable.
Some comments in a previous thread suggested that the refactoring support for Kotlin is not on par with those for Java. Has there been any change in that area?
Hmm, I been on a total of 2 kotlin projects, both went badly.
On the other hand, we have several hundred java projects, with a very low project failure rate.
Maybe java is missing some things that are trendy and you get used to using, the things we missed most in kotlin was working code that was easy to debug, repurpose and deploy.... Stupid issues like the compiler not working properly or just taking ages burnt to much developer time, and to hard to find staff to make it up.
But heh, maybe I'm old fashioned, it doesn't seem like a popular opinion. But nothing in this thought piece seems to address those fundamental issues.
I agree with you that it is an anecdote, but I don't see anything wrong with sharing personal experiences of using a certain technology... this is a discussion forum after all and not a scientific paper.
Same exact experience. I recently tried my hand at rewriting one of my old Java Android projects in Kotlin. Maybe it's just my brain getting older, but I found Kotlin development experience to be far less enjoyable than I was anticipating based on all the high praise.
Don't get me wrong, Kotlin has some nice language improvements, but overall I found it to be more confusing than helpful. A weirdly confusing co-routine implementation, new unintuitive syntax, lots of new keywords, etc.
I dunno. I'm sure it's a fine language if that's what you're starting with, but as a former Java dev that was actually productive, Kotlin was anything but a breath of fresh air.
When used in conjunction with named parameters with default values, it makes it possible to write function signatures that are significantly more readable than those written in Java, and with far less boilerplate.
I disagree with point 1). The real problem is not that immutable references are inconvenient to use, but that you cannot mark the whole instance as read-only and get something similar to the const-correctness that you can achieve with C++. There are tools that help with this, but having something either in the language or in the JVM/standard library would be much more useful.
Point 3) and 4) are in my opinion not that big of an issue. Annoying, yes, but not deal-breaker.
Null safety is really something that I miss in Java and what I consider the biggest flaw in the language. Again, there are libraries and tools that help with that, but it is not the same as something which is part of the standard.
119 comments
[ 3.0 ms ] story [ 210 ms ] threadJava in my opinion should evolve by keeping the original approach (a better C++) and leave programming language experimentation to the big three.
There are still pain points, but the language is leaps and bounds better than the Java 1.6 days.
The springification is what I mean by it being externalized and you generally don't see that in C, no. The pre-processor might be sort of the same thing but you generally don't bootstrap the entire thing using it.
But this is absolutely domain-specific, Java the language is indifferent to exposing a library that will handle incoming request and dispatch them based on some library logic, or creating a “traditional” program for the litany of other domains where java is used st.
If you'd chosen languages that permit abstractions in a real and usable sense, then you would not need this. In fact: with _one_ exception, I've never had a container in any Haskell, Scala or F# project ever. Do you think we ever needed one?
And this is my point: those other languages have seen _tons_ of development in the "abstraction arena" with (internal) DSL:s and super powerful frameworks for making powerful DSL:s. Java has had none of this because these concerns har all (poorly) handled at the container level.
The tacked on "functional stuff" is for sure cool coming from a Java backround, but they're pretty much useless.
This "containerizing" is very unique to Java -- I've never seen anything like it in the other various languages I've used. I won't even mention the total disaster of JavaEE containers.
My point, though, is not to throw up all over Java but rather to quibble with the statement that Java has somehow evolved since then. It really hasn't!
https://www.destroyallsoftware.com/screencasts/catalog/funct...
Basically you have business domain objects that have value semantics and are immutable. You have functions that manipulate these objects. This makes the business domain unit-testable. Then on the edge of the application you have imperative code that manipulates databases, serves requests, etc. It’s like Haskell but as a design pattern rather than a language.
In the JVM world, Scala and Clojure communities are adopting this approach.
It is essentially straight forward to do. You look at a bunch of code in a module and determine which parts do these kinds of things:
- data integrity, validation
- parsing, transformation between formats
- domain logic, decision making
- algorithms and data structures
This is your functional core. Note it doesn't matter too much if you have local mutations as long as they don't leak out. What you want to achieve is to have an interface where you can ask questions and you get the same answer for a particular question. This is certainly more productive if your language affords you with functional constructs, but its not strictly necessary to get the benefits.
You code these parts in a way that they are data in, data out essentially. This leads to super easy and reliable unit/example tests and generative tests. And this part becomes very simple and easy to reason about.
Secondly you have the part of your code that actually _does_ stuff, affecting your environment in some way.
- basically any kind of I/O
- cross cutting concerns between other side effecting modules and services (think DI and such)
- error handling, error reporting and logging
- caching, throttling, filtering etc.
That's your imperative shell (I just call them modules). This part you write in an imperative/procedural style and you call the functional core parts from here, never the other way around. You coordinate effects so to speak.
It is clear that you test these with integration tests, where you need to setup specific program state (or use mocks, but I try to avoid that) in order to find different code paths. This is harder to test properly but at the same time you also get to see everything that actually happens in condensed form.
The degree to which you extract "pure" logic from your imperative shell is actually quite flexible. You can go as far as defining purely functional FSMs to extract much of the control for example. I think it really depends on code size and complexity as well as performance implications. The cool thing is that you can be quite pragmatic and iterative about it.
Of course you can't have the pure/impure distinction enforced, but nothing that isn't a purely functional language is going to give you that.
The only really dumb limitation is that you can't have freestanding functions, but that's aesthetics more than substance.
I think Kotlin actually has the potential to dethrone Java on the JVM, it is that good.
Kotlin is not much different from Java, conceptually. It is basically Java 2.0. Where Goovy does away with compile time typing, and Scala bolts a Haskell onto Java; Kotlin is "just a better Java".
Groovy's lack of compile time safety is like throwing out the baby with the bathwater. It's just not worth it for me. Kotlin gets you most of the language features but without doing that.
Here's a good overview of differences:
https://www.baeldung.com/kotlin/kotlin-vs-scala
https://docs.groovy-lang.org/latest/html/api/groovy/transfor...
https://livebook.manning.com/book/groovy-in-action-second-ed...
I really don’t get it - I am fairly certain that feature wise Kotlin is actually a bigger, more complex language with many exceptions/edge cases. Scala is a really elegant language in that many of its features are solved by a singular base, for example implicits. And frankly, other than that it is a very easy language - programs written in it can be complex due to its high expressivity.
No way.
I believe pron can do a better job articulating the reasons then I can so here is his comment: https://news.ycombinator.com/item?id=24197817
Looks like Flix is in a galaxy of languages implementing Datalog, a variant of (or somehow related to) Prolog. I guess Flix is Turing-complete while Datalog is not?
Flix is a regular programming language, and supports datalog as a DSL basically, supported by the compiler and type system.
kotlinc is way slower than javac but damn, it's still pretty fast compared to what else is out there.
I've recently been profiling kotlinc and there are files in our codebase that compile at 5loc/s!
What? What about `foo == null`?
And there's 7-8 different annotations to mark nullability, all slightly different in some detail.
It's a bloody and messy hell.
Doesn't help you with third party libraries, but across an org applying that rule (and others!) typically ensures some consistency.
Looking at a method/property signature it should be explicitly and unambiguously clear whether or not null is an acceptable value - but in java there is no way to enforce this.
If instead the compiler can guarantee that a value will never be null, then you don't have to check, leading to cleaner code. Scala does this with the Option type, Typescript by forcing you to declare a type as `string|null` if it can be null. These sort of things make your code safer and cleaner.
... so preoccupied with whether they could, they didn’t stop to think if they should.
If a variable is const-declared, I react in exactly the same way and know that I don't have to look for further uses, which I find very valuable.
I even do stuff like this, which might be extreme but I find it coherent and nice:
The above const-declares the local pointer p, so that it can never be intentionally clobbered inside the function, which would leak the memory. Since it's the pointer itself that is constant, not the data being pointed at, there is no problem in modifying that memory, and the return value is not const-declared in any way.Syntax wise the languages are quite similar (before we get to type systems).
At first I really hated how similar the keywords were (something like let/const seemed preferable).
After a few years of Scala you get used to it.
In fact val somehow feels "nice" and var feels "dirty" so you avoid it as much as possible.
But "let" is already a thing in Kotlin (a scope function in the standard library).
In my experience, in practice "var" are an order of magnitude more rare than "val", so I'd be perfectly fine with "val" vs. "mutable". I wouldn't mind the verbosity - it would gently discourage the use of mutability, while making it very apparent wherever it's actually involved.
> in practice "var" are an order of magnitude more rare than "val"
Or rather, it should be that way. Agreed, and I think because the world has been moving more and more towards functional style over the past decades, I'd predict that the next big language that will make it to the mainstream will likely take the approach "immutability by default".
mut is a nice proposition, I'd certainly consider it if I were to design Kotlin
This is a long-debunked myth. See e.g. https://docs.microsoft.com/en-us/typography/develop/word-rec...
Though note that val and var are more similar in terms of their letter content than they are in terms of their word shape.
Does kotlin have them?
I would rather see the ecosystem adapt records. Or once Valhalla happens, one can probably have a near zero-overhead Property<T> field for properties inside a record, properly supporting everything required by Java Beans.
So it's not "true" reified generics as in C#.
You can't go "if (T is Whatever)" and put it in your SomeClass<T>.
This is not Kotlin's fault, just legacy of Java with which it has to put up in order to maintain full interoperability.
Switching back to Java feels increasingly awkward at this point. I can do it, but there are no use-cases where I would prefer Java over Kotlin. I certainly don't enjoy it.
Sure, they've added features to the language and they help but it's just not even close to the language ergonomics that Kotlin has.
The val vs. var thing is super relevant because this is actually something that is more cultural than language related. As the author points out, you could just litter your Java code with final and it would be similarly safe code. But the reality is that very few people do that. Java makes you do the wrong/less safe thing by default. Kotlin has this opposite culture where you feel slightly guilty/dirty every time you even make something a var. Having a difference between mutable and immutable lists and maps is likewise something that Kotlin inverts. In Java you could always make lists and maps immutable but they'd be immutable by default. With Kotlin it's the reverse: everything is immutable unless you make it mutable.
It's this safe by default attitude that pervades Kotlin that just shows up in a lot of places. Nullability as part of the type system removes an entire category of bugs. The only time I still see NPEs, it usually involves some Java library that I'm using.
Considering how easy it is to switch from Java to Kotlin, it's not surprising that you see essentially all the major Java frameworks come with excellent Kotlin support. I use Spring Boot with Kotlin. It's quite obvious to me that using spring without Kotlin is a bad move at this point. There's so much good stuff you miss out on that is added to Spring via extension functions. E.g. having co-routines sit on top of webflux, makes the whole business of using asynchronous IO with Spring a lot less painful. Without that, I'd probably still be doing synchronous IO.
IMHO, there's a more fundamental concept that everybody seems to be missing, which is that general purpose programming languages somehow fail at general purpose queries across trival, in-memory data structures.
A random example I like to bring up is: Can your LINQ-equivalent efficiently and idiomatically calculate both the "min" and "max" of a list without enumerating it twice?
Imagine you're writing some abstract library that can take any IEnumerable as the input(s). Will it scan a 1TB database table twice because the idiomatic coding style is:
This is such as basic, trivial example, yet C#, Java, Kotlin, etc... languages just fall flat on their face. Meanwhile, in SQL, a 48-year-old query language: But this is just scratching the surface. Ideally, all LINQ-style data inputs should be treated as something like a B-Tree index. Something that can be "walked" (not just enumerated!) efficiently using prefixes, ranges, and similar conditionals. Multiple queries should be able share such as "walk" to minimise the cost of streaming data in from memory... or wherever. Data should be partitioned across CPU cores just as efficiently, and the query language should make this natural.I'm yet to see a language that scratches this itch.
The amount of time I’ve seen it complained about, yet in practice I am very seldom hit by that (just a note, Haskell also erases every type yet it doesn’t bother anyone there).
So we guarantee you you'll get a String back. And hear this: we also guarantee you, 100%, it's going to be non null. And we guarantee you that the reference to that non null String is going to be immutable.
Oh but wait: maybe you'll get a RuntimeException instead...
A good example is the state Dart is in right now. Almost everything has been transformed to null-safety including most of the community packages and it works really nicely.
Some frameworks like JPA are not up-to-date with this methodology, sadly. But Java direction with immutable records is obvious and they'll adapt.
It will be shallowly immutable, but applying it recursively will make it totally immutable. The reason I say that is that in practice it is not that different than FP languages, they just differ in the ratio of FP data structures.
well, that's not exactly true, because extension functions can't access private fields.
https://news.ycombinator.com/item?id=27183571
If I wanted to mix in or use a new language, I would want something that was a substantial upgrade in terms of expressiveness and platform.
To me Kotlin is a bit like what Coffescript is to JavaScript.
On the other hand, we have several hundred java projects, with a very low project failure rate.
Maybe java is missing some things that are trendy and you get used to using, the things we missed most in kotlin was working code that was easy to debug, repurpose and deploy.... Stupid issues like the compiler not working properly or just taking ages burnt to much developer time, and to hard to find staff to make it up.
But heh, maybe I'm old fashioned, it doesn't seem like a popular opinion. But nothing in this thought piece seems to address those fundamental issues.
Don't get me wrong, Kotlin has some nice language improvements, but overall I found it to be more confusing than helpful. A weirdly confusing co-routine implementation, new unintuitive syntax, lots of new keywords, etc.
I dunno. I'm sure it's a fine language if that's what you're starting with, but as a former Java dev that was actually productive, Kotlin was anything but a breath of fresh air.
Point 3) and 4) are in my opinion not that big of an issue. Annoying, yes, but not deal-breaker.
Null safety is really something that I miss in Java and what I consider the biggest flaw in the language. Again, there are libraries and tools that help with that, but it is not the same as something which is part of the standard.