4 ms·
I'm currently working on a backend system written in Kotlin and the Reactor framework, and came to the following conclusions: - Kotlin's biggest problem is how
by haspok 6y ago
I'm currently working on a backend system written in Kotlin and the Reactor framework, and came to the following conclusions:
- Kotlin's biggest problem is how it handles nullability (that is, it simply enables you to leave nulls in your program). This plays extremely badly with classical FP, and Reactor specifically, because 1. the Reactor "monad" (Mono) can't wrap null and 2. flatmapping an empty monad is not the same as flatmapping on Some(null) for example.
- Kotlin has no syntactical support for flatmap/map, like Haskell or Scala.
- Kotlin has no pattern matching, inline types are still experimental, and it's type system is limited, it is clearly targeted at the UI/Android folks who are not interested in expressing their data in a static way. Making sure that people don't shoot themselves in the foot is great for the average Joe developer, but not so great for library writers.
Having said all that, it's still better than Java, but it's 2020, ZIO is past 1.0 and Scala 3 is around the corner. I'm really looking forward to 2021...
- lostcolony 6y ago"It's better than Java" - I've said that about numerous languages older than Java. It's definitely a lowest common denominator.
- dwohnitmok 6y ago1. Isn't that just a reflection that union types don't form monads? I find it's not a big deal most of the time and when it is just convert so that you have legitimate maybe types, for example like the Option type provided by Kotlin Arrow. You don't lose type safety either way since the type system won't let you use one in place of another. 2. I assume you're talking about `do` notation (bind/return + ap/pure if ApplicativeDo is turned on) and `for` comprehensions (flatMap and map + filter/filterMap if you use `if` statements) respectively? It's a minor pain that they don't exist, but ultimately not something that I find is a huge deal. Besides I'm not actually a huge fan of those syntaxes. They force you to rewrite code in A-normal form, which means that wrapping monadic types can cause large rewrites to your original code. I much prefer an approach like Scala's monadless (https://github.com/monadless/monadless https://github.com/monadless/monadless), perhaps with additional syntactic markers indicating that this is a syntactic macro rather than a function, which does not force users to rewrite their code entirely in A-normal form. Kotlin has something similar to this (coroutines) which is what Arrow Fx uses, but again, like many other things in Kotlin, it's hard-coded to be only one thing and cannot be generalized to other monads. However, it's a nice middle ground for marking side effectful code and syntactically much nicer from my point of view than either do notation or for comprehensions. 3. Honestly pattern matching isn't a huge deal for me. Destructuring gets me a lot of what I want and the much bigger deal is exhaustive sum type checks, which Kotlin provides in its `when` declarations. Experimental inline types are fine with me. They're stable enough and I've never really regarded `AnyVal` in Scala to be any more stable (it fails in a bunch of corner cases to the point that some codebases entirely eschew it in their style guides). Opaque types in Scala 3 will hopefully fix this and Haskell's newtypes are great. The two main differences in type systems for day-to-day code are lack of higher-kinded types and lack of typeclasses. As Elm and F# demonstrate I don't think either are fatal blows. Speaking of Scala 3 I really hope it goes well. I'm still a tad nervous about introducing indentation-based syntax since it seems like a needlessly divisive choice for little benefit, but we'll see how it goes.