4 ms·
As a scala user, I laugh when I hear Kotlin users saying they are doing functionnal programming.
by gbersac 7y ago
As a scala user, I laugh when I hear Kotlin users saying they are doing functionnal programming.
- The_rationalist 7y agoCan you give me a list of kotlin missing FP features that Scala has? Then I might consider it. Please, distinguish what Scala has over kotlin separately than what cats/scalaz has over arrow. You will be surprised to see how little the list is.
- lmm 7y agoFrom lower-level to higher-level: Real immutable collections (might be available in a third-party library for Kotlin, but not used in the standard library or any of the ecosystem) Well-behaved option/maybe type (arrow implements one, but it's not used in the standard library) Either type used in the standard library where it makes sense (arrow implements one) First-class (monadic) comprehensions in which the language behaves as normal (arrow makes an impressive effort, but it has rough edges from not being part of the language, and its implementation inherently relies on capturing pieces of imperative execution, so you can't rely on it for monads that do control flow e.g. a backtracking/nondeterminism monad). Practical way to implement typeclasses for third-party types, that will be understood correctly by the whole ecosystem (IDEs/debuggers/etc.) Together with the above this means you can enable comprehensions for any standard library or third-party type for which they make sense, not just libraries that were built on top of arrow (e.g. standard collections, third-party database libraries, types from the Java or Android standard libraries). Arrow's annotation processing is a poor substitute for being able to do this in a first-class way, and even if it were built in, the typeclasses are a lot more cumbersome at the point of use. Practical way to write tiny, composable functions that rely on HKT/typeclasses. In Kotlin if you want to write a one-liner you still have to cart the typeclass instance around, which might seem trivial but is actually a huge barrier to working in high functional style, where you'd write hundreds of tiny combinators that each do one simple but really generic thing. Monad implementation for the standard way of representing presence/absence, the standard way of representing async, etc. Even with annotation processing, it's impossible to ever implement a monad instance for Kotlin's nullable types (because they're not well-behaved). Iteratees or equivalent - a way to represent possibly-async transformation pipelines such that even the middle of a pipeline can be a well-behaved, reusable value that we can use a lot of general-purpose functions on. Record types (relying on Shapeless) and a way to convert back and forth between case/data classes and a generic representation of them - fulfills a similar use case to "compile-time reflection". This really unlocks a lot of functional power when you combine it with applicative / monadic traversals of those generic representations - it lets you walk an object graph, doing something effectful at each node, in a completely safe, managed way. Recursion-schemes style traversals - representing different kinds of recursion as composable values. This makes it a lot more practical to do things like tree transformations in a safe way, because it becomes ok to have 20 subtly different types of tree representing different intermediate stages of your operation. Kind polymorphism (relies on kind-projector in Scala which has similar limitations to Arrow) - makes the above a lot more effective, as you can write functions that do the right things for generic and non-generic trees (for example). The first half of this is more a matter of degree than of kind, because an ecosystem is more than a language. An imaginary Kotlin ecosystem in which everyone used immutable collections and everyone used arrow optionals instead of nullable types might approach the usability of Scala on this front. But the Kotlin ecosystem that actually exists does not operate this way - and, crucially, there's no way to interoperate between working this way and not. Scala was able to overcome this barrier by having implicit conversions and good typeclasses, so even if you have to use a third-party library that doesn't follow functional conventions, you can still enable functional operations for it. Kotlin can't do that; extension methods don't allow the types they're attached to to implement interfaces, so the only way to treat a third-party type as having some functional support is to plumb a typeclass instance for it around manually as arrow does. And beyond that, trivial inconveniences matter. It's possible to write a well-behaved Either type in C++, but no-one uses them because it imposes so much code overhead that it's not worth it. Ultimately Greenspun's law applies - you can implement any language in any other one - but at the point where you're working in what's effectively a Scala interpreter written in Kotlin (which Arrow is halfway to being), you're gaining very little of Kotlin's benefits (in terms of tooling, ecosystem and so on). And the things in the second half are more fundamental. Arrow is an impressive effort in working around the limitations of not having HKT, but it can only go so far; at some point you need real HKT in your language. (It's the same story in Scala one level higher - the things you can do with kind-projector are really impressive, but they're also no substitute for using a language with real kind polymorphism). And if you want to actually unlock the power of functional programming, it has to be practical to avoid using anything that breaks the rules of the language - reflection, bytecode manipulation, annotation processing, macros - and for that you need an equivalent of Shapeless (or Frunk in Rust), or ideally a proper record system. I'd love to be optimistic about Kotlin - there's a lot that it cleans up - and certainly I hope that the language will eventually adopt HKT. But even then, the ad-hoc approaches of the language's early design will always be a millstone around its neck. I mean, fundamentally, Kotlin collections are mutable and nullable types are ill-behaved, and I can't see the ecosystem/culture ever managing to go through a wholesale conversion to using immutability and proper optionals (contrast this with Scala where mutable collections and null exist but you'll never see them in a mainstream Scala library). And that alone is always going to make it less pleasant to work with than Scala.