Hacker News new | past | comments | ask | show | jobs | submit login

This understates how functional Kotlin is—you wouldn't say that Scala is generally OOP with some functional support just because it was built on the JVM. Scala's clearly a functional language with some OOP support.

Kotlin, in turn, is very balanced. The standard collection library uses OOP syntax (chains of method calls), but is extremely functional in its philosophy towards how we think about manipulating collections.






I think a comparison to Scala is apt. Working with both languages makes it quite clear to me that Scala is most certainly much more functional than Kotlin. Kotlin is really not very functional in practice, in that it really doesn't encourage a functional style, nor is it optimized for the patterns that are common in functional programming.

Scala has `Try` and `Either` for modeling domain failures as values as opposed to throwing (unchecked) exceptions, which are side-effecting. Scala also has for-comprehension syntax built in to make it more convenient to compose and chain fallible operations that use `Try`, `Either, `Option`, etc. Kotlin's `Result` type is not designed for modeling fallible operations (not a sealed class, no type parameter on the error variant, error must be `Throwable`, etc), and Kotlin does not offer convenient syntax like for-comprehensions despite being more than able to (given that many of us have implemented near-perfect analogues to Scala's `Try` and for-comprehension syntax in Kotlin). Similarly, no official Kotlin libraries or APIs ever return errors as values and always opt for throwing exceptions instead.

Scala's collection types are implemented as persistent collections, which are optimized for cheap updates. If you want to avoid direct mutation in Kotlin, you have to make a full copy of a collection.

Scala's mutable and immutable collection types are actually distinct from each other and cannot be used interchangeably. In Kotlin, List<T> is a supertype of MutableList<T>, which means I can pass a MutableList into a function that expects a List. That means that the list can be changed in another thread while my function is running, so I can't even assume that checking `list.size` at two different points in my function will return the same value.

Scala has actual type classes. Kotlin has extension functions which are not nearly as useful (and they have very surprising semantics when it comes to static vs dynamic dispatch). Type classes are certainly not required for functional programming with a statically typed language, but it definitely helps when it comes to modeling things without needing to lean on writing more classes and/or utilizing inheritance.

Also, Kotlin's "functional" APIs on collections are much more janky than Scala's. For example, if I have a `Set<T>` and I call `.map((T) -> R)` on it, Kotlin will give me a `List<R>` while Scala will give me a `Set<R>`, which makes way more sense.

Kotlin is cool, and it would be dishonest for me to say that it's not at all functional, but after having worked in other many other languages, I'm very comfortable saying that Kotlin is an OOP/imperative language first with some functional stuff added in (sealed classes, convenient lambda syntax, top-level functions, and some of the typical collection combinator APIs). Whereas Scala is quite clearly designed to be actually GOOD for functional programming without taking an extra-hard performance hit.


Exceptions are great. Whats not great is not having them expressed in the type system via checking. I think Kotlin's greatest mistake is not improving upon checked exception handling. Though it looks like they're going to be moving forward in the future with errors as values via union types [0]. Scala also has some experimental work around putting exceptions into the type system via capabilities [1]. I really like Scala's solution because it lets checked exceptions work across higher order functions and Scala has enough syntax sugar to make handling exceptions pain free.

[0] https://youtrack.jetbrains.com/issue/KT-68296 [1] https://docs.scala-lang.org/scala3/reference/experimental/ca...


I have LOTS of opinions about this topic, but I'll try not to ramble.

I always found checked exceptions (Java) to be mostly fine/good. I sincerely believe that a lot of the hate for them in the last decade is just cargo culting. I get a small dose of schadenfreude when I see someone doing mental contortions to simultaneously explain why Rust's Result type and handling (and similar features in other langs) is awesome, and Java's checked exceptions are terrible and definitely not 95% similar in DX and semantics...

The one point against checked exceptions for me is that if I'm trying to model a domain failure, it really doesn't make sense to collect a stack trace. For example, if I'm writing a logIn function that takes a username and password, then it's totally normal for the username and password to be invalid. Why would I want to spend the CPU time collecting a stack trace when someone simply typed in an incorrect password? Do we want to collect a stack trace when the password is correct and the user gets logged in?

So, in that sense, I do have a small preference toward expected failure modeling to be somehow different from "true" "exceptions".

I do also agree with you that Kotlin's biggest original sin was to throw away checked exceptions without replacing the concept with ANYTHING. Of course, as you've pointed out, they're backtracking on that somewhat by trying to add this concept of errors as a kind of ad-hoc union type. (aside: they also backtracked on their choice to not have type classes because "extension functions are good enough" by trying to do context receivers, which is ending up being really hard and probably more complex than just doing damned type classes in the first place...)

I do kind of like the direction that Swift is moving with finally adding specifically typed throw signatures (essentially Swift now has checked exceptions, but they're aren't actually traditional exceptions because they don't collect stack traces and unwind the stack- they're just syntax sugar around a Result/Try type).

But, my prediction is that the pendulum is starting to swing back in favor of checked exceptions. In the next decade we'll continue seeing languages adopt mechanisms that are essentially checked exceptions. But, they will be slightly different and definitely called something else so that we don't have to admit that we were wrong to shit on the idea for 15 years.

EDIT: Also, I do follow Kotlin developments closely, but I haven't actually worked in Scala for several years, so I had no idea about this capabilities idea. Thanks for the link.


I have a ton of opinions on exceptions too, mostly because I love them.

FWIW you can override the stack trace collecting behaviour of Java exceptions. Not collecting the stack trace makes exceptions really fast and thats actually how Scala is implementing their boundary/break feature. I do kind of wish that Java could backtrack and that the stack trace would only be filled in on RuntimeExceptions that are true panics.

I really feel like Java just needs investment on the language syntax to make checked exceptions good. Things like `try!` or `try?` from Swift would be nice and taking Scala's try { as an expression with case catch blocks would make it really fluent. I think most devs can agree that they want to know what errors can happen, but currently in Java its just a pain to deal with them. Brian Goetz originally had some ideas around evolving the switch construct for this [0] so at least we know making exceptions better is on his radar.

[0] https://openjdk.org/jeps/8323658


> FWIW you can override the stack trace collecting behaviour of Java exceptions.

Could I ask you to point to an example?


    class MyException extends Exception {
        @Override
        public Throwable fillInStackTrace() {
            return this;
        }
    }

Persistent collections for Kotlin are actually implemented in a first-party library: https://github.com/Kotlin/kotlinx.collections.immutable

I use it in almost every project because it works so well with StateFlow: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-corout...


That's not what I would call a "first-party" library. Yes, it's written by JetBrains and under the Kotlin project umbrella, but we have to add it to our project like any other dependency.

I can install a persistent collections library in any language. I know for a fact there are persistent collections libraries for Rust, JavaScript, Java, etc.

But since none of those are built-in, they aren't used by the standard library APIs, and they won't be used throughout the wider ecosystem.

And, I'm still not willing to call Java a "functional language" because I can install a third-party library for persistent/immutable collections and hopefully avoid the built-in collections as much as possible.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: