NOTE: This post was originally intended to provoke a discussion, and despite its length, the original goal is still the same. Please, share your feedback
My experience with the JVM dates back from around 2010. Java in those days was a bit different from todays' standpoint. Java 6 was the current standard at the time, but most projects I got to work on, were stuck between Java 4 and 5. Those were tough beasts. More verbose and tedious to write code in.
At that time, we began searching for a way to release the burden of writing code, but keep relying on the vast adoption of the JVM. Groovy and Scala were the two alternatives offering more language expressiveness with less boilerplate and ceremony. And, while Groovy somehow never picked up the self-esteem to be seen as something more of a scripting language for build orchestration and runtime in-app plumbing, Scala looked like the bold and sexy future of the JVM.
That is, until the language and its community ideologically drifted apart from Java. Although most of the existing Java libraries at the time were compatible and accessible from the Scala runtime, those were seen as archaic, tedious to work with, and simply not built "the Scala way". This slogan was used to justify the building of a whole new ecosystem of tools, libraries, and frameworks, written from the ground up with Scala in mind. It is safe to say that nowadays for every Java library out there, one could find two or three Scala equivalents written "the Scala way". I am not much of a Scala expert, but IMHO, this has led to a total separation between the two communities, each re-inventing the wheel its own way.
Kotlin was supposed to help solve all of that. Having joined the game late enough, JetBrains put a bet on Kotlin's inter-operability with standard Java code. To achieve this, a language has to be perceived as a companion, and not as a true paradigm shift. For the three years I have worked with and followed Kotlin's development, there have been only a few minor occasions when the inter-operability did not work on 100%, and those were more or less edge cases. It seemed that had finally reached the golden mean, where multiple language paradigms can co-exist and their communities cooperate in order to achieve a common goal.
Yet, this is not quite what is happening. I have seen the birth of a Kotlin-first community of developers, preaching things "the Kotlin way". Soon enough, tools, libraries, and frameworks started appearing, having perfectly working equivalents in Java, but written in "idiomatic Kotlin". I started wondering where I had seen this before ๐ค.
Don't get me wrong, I am 100% for challenging the Status Quo, but by stepping on what already works well, and not merely throwing it off-board, because it was not built "the XYZ way".
And what do you think?
Top comments (21)
Mmm, what is the point of being citizens of the JVM and being interoperable if you end up rewriting stuff that already exists just because of ego? (Because I suspect ego is in play here :D).
Could there be legitimate reasons to rewrite something? Do they go faster if written in Kotlin? I don't know anything about it so I might be missing something. Because if the only reason is "I don't know Java, so I'm rewriting this in Kotlin" that seems very weak (and also it means you're likely going to introduce bugs and solve issues that were already solved in the first place :D)
ehhe
I know (a lot of) java (12+ years! Woof!), and I find myself having to rewrite a bunch of stuff because it doesnโt support immutable, fluent, functional, or generic idioms quite as nicely.
Once you peel back that first layer of over-interfaced factorybuilderbuilderfactory pattern that permeates the older java libraries, you end up having to do a lot of cleanup to make the new facing work properly. So then you might as well step one level in and clean that up too!
Oh dear, Iโve rewritten the whole thing.
Thanks Jon, why not limit yourself to writing wrappers or faรงade libraries instead of rewriting? Hiding the complexity of the Java-ish code in nicely thought out APIs. This way you have, for example, kotlin-http-client which explicitly depends on a Java open source lib and tracks its changes. This way you don't split the community.
Is this doable? Or is it the long term goal to rewrite as much as possible in kotlin and then say bye bye to the church of Java ๐?
You had to pick one of the worst examples of Java, didnโt you?
URL.toStream sucks and is not intuitive to read 6 months down the line. (And letโs not forget having to add in your own retry policies, header and response code parsing, turning off ssl verification, etc.)
Apache HttpClient is old and feels old. It gets the job done, but at the cost of having to implement anonymous interface implementations whenever youโre trying to mess with settings.
The biggest kotlin http client repo I can find is fuel. This library is... alright. Weโre running into trouble debugging problems with it because it tends to barf deep inside HttpClient, which makes me feel like I should have just bit the bullet and gone with using the Apache jar straight up.
Another source of fun problems is java reflection. Specifically bean reflection. This is most noticeable when attempting to use Jackson to serialize the following class:
outputs:
Yes, I know thatโs not proper kotlin idiom, but itโs non-obvious to me that Jackson would just convert them all from Pascal to camelCase. (The reason is that itโs the bean spec! Use annotations and make your kotlin ugly again! Yes, one of the things driving me to Kotlin is the explosion of Annotatiomania and Lombok. Used sparingly, annotations are good. Using them to code-gen, do too much abstraction, or hack the language are bad.)
Yet another fun one: in Apache Tinkerpopโs gremlin library, thereโs a class named
__
.If Iโm writing a couple lines of interop, then yes, a nice facade will do. Just sweep the mess under the rug so I donโt need to see the FizzBuzzIteratorCheckFactoryBuilderContexts bleeding through every so often.
Itโs just that used long enough, the facade takes on a life of its own. Debugging how the facade works and then having to context switch back to 1.6 era Java code when diving through undocumented code is even more taxing!
(And full disclosure, I really wish I had the time to just write all this crappy code in Haskell or Pony instead. Writing Kotlin is better than writing Java, but itโs not truly taking the shackles off)
Hi Jon, it was just the first thing that popped in my mind because it's a classic library every language has, I don't have Java experience :D
Thanks for taking the time to make me understand it's not just a simple matter of writing interfaces
That's true indeed. A facade in a sense it's an anti-pattern, because it's easy in the beginnig but if not treated like a giant
TODO: refactor what's inside
it will give you pain in the endI found out just now by googling that there's a version of Haskell for the JVM called Eta but maybe what you're looking for is... Clojure? Even though it's dynamic...
Hickey is right about a lot of things, but heโs wrong about types and nulls.
Types eliminate an entire class of errors.
Nulls destroy type systems.
Well, Clojure has types, they are just dynamic. I feel like the argument of static vs dynamic typing is as old as programming. Both camps have valid arguments.
I tend to agree more about the problems brought by null, it's one of those ideas that seem genius in the beginning, until they aren't.
He's wrong about how much productivity is lost/gained using dynamic vs. static.
Here's an overly simple and contrived example:
A function that returns the first letter of a string.
A la haskell:
A la clojure:
I left a bug in the Clojure code to illustrate my point. In haskell, I know exactly what happens when I try to call:
firstLetter [1,2,3]
. It doesn't compile, and my IDE/Vim/repl tells me as soon as I hit save.(first-letter `(1 2 3))
has no such problems, it can even be inserted in the repl as part of a macro or definition without revealing the problem. But as soon as it gets run..."The first letter of (1 2 3 4) is 1"
. Hmm, seems like it's not returning the first letter at all! Oh wait... that's not it...Yes this example is contrived and overly simple, but static typing systems prevent this entire class of bug from happening, so what I lose in refactor time I gain in not having to do type checking as part of function logic.
Hickey's key argument is that I can't change my contracts on my users, and that's fair. However in practice I find the following:
It's funny because one could make the exact opposite argument.That's why I was saying that static vs dynamic is an argument as old as programming and a non starter in a sense. There are advantages in both.
My experience of "productivity" is higher in dynamic languages because I don't have to deal with telling the compiler which type is every single variable I use. REPLs also tend to help there. It might also be that I wasn't using the right statically typed languages :-)
The example you're using between Haskell and Clojure is definitely valid, Haskell seems clearer (though I'm not versed in either of them :D) but let me answer to your points, which make me think we have had different experiences in sw development, which is great because there's not a single way to do things.
I've never been asked to write or not to write tests. I've always written them, regardless of which language we were using. Tests are part of the software and I estimate them as such :-)
Again, I went from static to dynamic and to me the first dynamic language was heaven on earth, I have bad memories of statically typed languages at the beginnning of my career :-) I like them more now.
Most dynamic languages also tend to be easier to teach in my experience, exactly because you have less stuff to read in the source file (which is always a plus) and to explain and because you can change contracts in a easier way
You can do the same in dynamic language. Changing contracts is always easy if you're the sole consumer of the code. BTW duck typing in dynamic languages tends to help here. A real world example: at some point in Python 3 they changed the internal type returned by the function
range()
from a list to an iterator. That didn't break because they behave the same way to the consumer 99.99% of the time, they just uniformed all functions returning iterators in the standard library IIRC.Yeah but this is the same with dynamic languages. Dynamic doesn't mean "I break the contract without telling you and now it's your problem". Good programming practices usally span between types of languages.
Cool, but Lisp dialects, if I'm not mistaken, are all dynamically typed
Elixir seem to be having a good run, it has pattern matching! No JVM though. Maybe your ideal language is a ML for JVM. Ocaml-Java seems to be dead unfortunately.
Sorry, I'm not sure what is that. I'm going to look it up :-)
The general answer to rewriting libs in idiomatic kotlin is often to provide a more concise API for kotlin consumers. In many cases this could be achieved just writing extension methods and many libraries fall in this category such as core Android having many kotlin extensions, gson being another example. The strong uptick in libs lately is for kotlin MPP which I believe provides what you really want. Kotlin can provide the cross platform foundation and existing libraries on target platforms can help acceleration of development. For instance writing code that runs on node, android, and iOS. In this case you will end up using many platform specific libraries which is terrific while also mixing in new libs that simplify multiplatform.
Well, I think it's completely okay to make things the idiomatic way. There are certain things that, while perfectly working in Java, are simply too cumbersome to use and can be vastly improved. My favourite example of this is the new Android KTX that are basically extension methods over what already exists.
Like this:
Versus this:
So of course, the Java way works perfectly, but it's also a pain in the ass to write. Isn't it why Kotlin was made in the first place? If everything was great with Java I don't think there was any reason for Scala or Kotlin to exist.
Nitpicking your example, the differences between the two methods is hardly down to intrinsic ability of the languages. The Kotlin version is just a wrapper around the Java one, and a utility class can be written in Java to the same end.
The Kotlin KTX libraries are really more or less apache utils for Android, and would be just as useful written if written in Java, the advantage Kotlin offers in this regard is solely the benefit of extension methods instead of static utility functions which is more of an idiomatic plus than a functional one.
You're right, however I don't think we'd have any of these libraries in Java since that's not an idiomatic way of writing Java.
Ultimately what I meant is that what Kotlin brought to the JVM was another way of writing code that was compatible with the huge amount of libraries that exists in the ecosystem; and of course when people realised that they can write libraries taking advantage of some features of the language that makes thing easier, they'd do it.
Maybe a better example of this are Anko or Koin since both are libraries that feature a DSL built on top of Kotlin.
I think this is really important. I will die on the hill that Kotlin is more enjoyable to develop with compared to Java, but I don't think we need to reinvent the wheel just to do it "the Kotlin way".
A great example comes from my own experience at my current employer. We have a
BaseActivity
class that is in Java, that could be shortened a lot by switching to Kotlin. We would get null safety, we would be able to use awhen
instead of if/else, but we haven't converted the file.We haven't converted it, because it works fine, and we rarely touch this file to begin with. If we reached a limitation in Java, or we found ourselves touching this file regularly to the point that we'd benefit from using Kotlin, that might be a different story. However, I feel that with this file and in some of the examples you're referencing yourself, that's just not the case.
Mmh, that's a very valid point.
There is no single answer IMHO.
Sometimes it's useful to reinvent the wheel and to break with the Java world.
Coroutines is probably the best example.
Kotlintest is so much better than junit.
DI in pure Kotlin is so much simpler than dagger.
Generally the best approach is more to add on top of the existing Java library the few things that makes it nice to use from Kotlin.
The logic of making it nicer for Kotlin can definitely be pushed too far. Kotlinpoet for example has all those builders to make it usable from Java. Ain't that obsolete and clumsy? I thought so. Not anymore. It's not a big deal in practice. Not splitting communities is more important.
kotlin should have never been created, because scala exists. of course it's going to get its own libraries and grow a new ecosystem. better just to come over to scala, because we've already figured out most of the hard stuff the kotlin-using kiddies will be discovering soon
Kotlin was invented because scala exists.
The thing that drove them over the edge was the compile time loop. Their secondary goals were to make the language simpler than Scala, and to add features Java developers had been asking for in Java itself.
infoworld.com/article/2622405/java...
And yet they've ended up with a language with many more special cases than Scala and poor interop with post-8 Java (in particular with the decision to integrate null tightly at the language level just as Java was moving away from it), just as many predicted at the time.
Many existing Java libraries are written in ways that make them impractical to use from code that follows modern good practice (whether you call that practice "functional", "SOLID", "idiomatic kotlin", "idiomatic Scala", or something else). It's good to write replacements that are more amenable to a better code style, and this isn't really a Java-versus-Kotlin problem - I've seen the same thing happen in Python where people write new libraries that offer the same functionality as existing libraries but in a more functional-idiomatic way.
This does mean the pitch for Kotlin was always based on a lie though. Scala already offered perfect JVM interop, but this isn't and will never be enough to bridge the gap between a mutation-oriented ecosystem and the style that we now realise is better. Kotlin was always doomed to become either a trivial syntactic sugar over Java, or a language that offered substantial benefits but at the cost of forking out a separate ecosystem, exactly as Scala did and for exactly the same reasons.
Of course Kotlin is now in the same place that Scala was 5-10 years ago - the same choices still have the same results, and it's impossible to deliver the benefits of Scala without paying the same costs. Being 5 years behind Scala is not such a bad place to be, but you're better off in today's Scala, and likely always will be.
Use whatever you like. Kotlin make it easy to use existing no kotlin code kotlin way by writing some extensions and so on... You don't need to rewrite library, just add some code for it.
Porting to Kotlin also gains you multi-platform code sharing. I'm highly motivated to port existing Java domain models and serialization DTOs into Kotlin so that I can build an iOS framework from it.