Hacker News new | ask | show | jobs
by lolinder 655 days ago
Kotlin's standard library has ruined me for other languages, especially its collections library. The consistency and comprehensiveness of its approach to collections is unmatched in any language I've tried, including all the big name functional languages. It's hard to get across what's so great about the library in writing because it's not just one standard library function, it's how they all interact with each other and how they interact with the language design—you really just have to try it to understand. The net result is that transforming data from one shape to another flows effortlessly, with the dot operator seamlessly connecting a stream of transformations. The fact that it's the dot operator also means that you get really great autocomplete to help you on your way.

Python, meanwhile, has always felt pretty awkward to me when it comes to data transformations. Comprehensions are okay, but they feel like they are special casing what should be a bunch of standardized helper functions operating on lambdas, as a sort of ugly workaround to the fact that Python refuses to implement proper lambdas. And when you can't use a comprehension, you're stuck with a pretty awkward collection of helper methods that are hard to find and use correctly and which are severely handicapped in expressivity by the lack of a proper lambda.

4 comments

That's interesting. I've heard complaints about Kotlins standard library in comments like this[1]. I understand they may be nitpicks but they seem annoying in practice.

[1] https://www.reddit.com/r/Kotlin/comments/mh2z5u/comment/gt2n...

That's on a thread that specifically solicited complaints though.

These are all reasonable but some are just lack of familiarity with the JDK standard library, or the reasons why things have to work that way to begin with.

For example, ArrayList not being immutable/thread safe. Although there are collections libraries that give you snapshot based collections, like this one:

https://github.com/Kotlin/kotlinx.collections.immutable

... I've never seen anyone use them because this is almost always the wrong design. Atomicity is usually needed at a coarser grain than a single collection, at which point you're needing to think about locking or transactions anyway, and if it isn't then the JDK standard library already offers concurrent lock-free lists or Collections.synchronizedList() which will give you the same effect. Having an object be mutated out from underneath you by a separate thread is a possibility of basically every language with shared memory. Only Rust tries to solve race conditions in the type system and its solution introduces many other problems.

He also complains that integer width/signedness casts only offer help from both the type system and the runtime! That's pretty good compared to other languages. Then he complains unsigned types are about the underlying bits not the semantic meaning of the number - well, yes, this is unintuitive but exactly the same as every other language because of the weirdness that inherently emerges when mixing signed with unsigned types. Java refuses to add unsigned numbers at all and they have their reasons for that! Unsigned numbers are really only meant for working with binary data formats, not encoding that something can't be negative. Use a jakarta.validation with a framework like Micronaut or Hibernate Validator if you want that.

Likewise for date and times sucking. If you use the long since deprecated classes designed in 1995 then maybe those suck by modern standards, although they're great for beginners. So don't use them: java.time is a modern package that treats timezones rigorously, at the cost of being a bit harder to understand.

Hmm, coroutines are definitely a bit of a mess, but in ways that aren't super relevant when you just want to use them and not implement a framework on top of them. They definitely sacrificed implementation simplicity in favor of interface simplicity.

> don't even try to tell me that anyone uses sealed classes in practice

I use sealed classes for errors all the time.

> Nothing is concurrency safe.

Yes, but I know of no stdlib of a serious alternative that is, so I don't think that's a major concern. Don't use concurrency and you're no worse off than Python (the alternative here), and if your point of comparison is Java or similar then it's the same story there.

> All of the numbers suck. The fact that I can just call Long.toInt().toUByte() and lose a bunch of information and/or wrap a negative value into a positive value, etc, without any kind of help from the type system (maybe returning nullables) or the runtime (throwing exceptions for truncation) is gross.

Similar to the above: yes, it could be better, but it doesn't bring Kotlin's stdlib below any other major language I'm familiar with. Heck, even Rust lets you do those downcasts without a word [0], you're just supposed to know that downcasts can lose information. Lints can help you here if you care, but I don't think a language gets points docked for not having them by default—there's a balance to be struck between too few and too many explicitly-typed failure cases.

> The Map API sucks. Map::getOrElse is literally implemented incorrectly-it will call the "or else" function if the value is present in the map but is null.

That... is fair. I've never actually noticed it before, but it's wrong. The rest of the Map API has always been good for me, though.

> Dates and times suck.

In every language ever.

> I don't like how the default for the collection combinators is to be eager.

They acknowledge that they have no answer here, and neither do I. There's no pattern for Kotlin to follow because only Haskell does lazy-by-default, and Haskell isn't a model most people would want Kotlin to follow.

All in all, I read a comment like this as someone reaching for the things that irk them in a language that they actually really like—which means the items that irk them are either extremely small or actually just broken in all major programming languages.

[0] https://play.rust-lang.org/?version=stable&mode=debug&editio...

> I don't like how the default for the collection combinators is to be eager. Isn't this solved by using Sequences instead?
Yes, but I took their use of "default" to mean that there's slightly more friction to a sequence. It does seem a bit petty given how little friction there is, though.
Thanks for your insight!
Those are literally the same as Java, they’re impossible to fix without breaking interoperability with Java.
No one is arguing Java is expressive.
Couldn't agree more if I tried.

Scope functions are also great.

And the syntactic sugar where `foo({ a -> a })` and `foo { a -> b }` are the same makes code so much more readable.

I've done Python for a project at a previous job for a few months and it made me realize just how awful Python is, especially because you can't chain functions on collections as easily as you can in Kotlin. I also made me realize that I don't like dynamically typed languages.

> And the syntactic sugar where `foo({ a -> a })` and `foo { a -> b }` are the same makes code so much more readable.

That's 1-to-1 copied from Groovy by the way :)

Always strange how genes survived over time.
> `foo({ a -> a })` and `foo { a -> b }` are the same

I am new to kotlin, so I may be missing something obvious, but shouldn't it be "a->a" in the 2nd case, too?

You're right, my brain farted somewhere in the middle.
> Scope functions are also great.

They are useful but I'm not sure they carry their weight. I have to look up the differences often, maybe they're just terribly named?

> Kotlin's standard library has ruined me for other languages, especially its collections library

Scala has it beaten by a long shot, in my opinion, and is hands down the best of any language around collections.

> Python refuses to implement proper lambdas

Python has lambdas. What do you mean by "proper" lambdas?

Lambdas in Python are intentionally second class citizens in that they cannot be full blocks, only a single expression. Technically there's a workaround in that you can define a named function and then refer to it later, but that's enough of a hassle that using lambdas in method calls is much less common in Python than it is in Kotlin.

In Kotlin, much of what would normally be special syntax structures are just function calls that get passed a lambda. That's not possible with Python's single-expression lambda functions, so you get special syntax instead for things like comprehensions.

Multi-line without ugly “lambda” keyword.