4 ms·
It sounds like you've already made a choice, but on the off chance you haven't I would suggest Kotlin. I would recommend Scala in second, but you really need a
by dwohnitmok 6y ago
It sounds like you've already made a choice, but on the off chance you haven't I would suggest Kotlin.
I would recommend Scala in second, but you really need at least one person who knows Scala well to navigate the wildly divergent styles of Scala programs out there.
Kotlin gives you sum types via sealed classes. In fact it's basically the bare minimum of Java + sum types + some syntactic sugar.
It has rock solid tooling due to Jetbrains support. It has access to the entire JVM ecosystem. It has a huge amount of learning materials out there. And it's got all the resources on how to deal with web CRUD problems you could ever imagine.
It's not server-side Elm by a long shot, but to be fair, Elm would suffer from all the same problems you listed.
- yawaramin 6y agoEh, for someone who likes OCaml and sum types, I don't think they would be wild about Kotlin. It doesn't have any generalized pattern matching, for example.
- dwohnitmok 6y agoYeah, and its ecosystem loves mutability, but with discipline in your own codebase you can get 80% of OCaml with access to an amazing ecosystem and tooling. After all nested when expressions aren't the end of the world...
- lostcolony 6y ago'but with discipline in your own codebase' If I had that (and, more importantly, trusted every other developer I work with to also have that) I wouldn't value immutability enough to look for it in my language choice.
- dwohnitmok 6y agoIt's a bit more subtle than that. Almost every language has a sliding scale of discipline rather than a binary one. E.g. you could decide to write extremely mutable code in OCaml, but it's highly discouraged and the happy path is generally to default to immutability. So you need comparatively little, although not none, discipline to have a pervasively immutable codebase, since there's nothing preventing you at a language level from introducing crazy mutation everywhere. Haskell goes further, where you have to use functions named "unsafe*" to introduce mutability. Something like Elm goes further still, where, barring bugs in low-level libraries or heavy abuse of the JavaScript runtime (e.g. redefining prototypes of basic types), you essentially have to fork the compiler to introduce mutability (Dark moved away from Elm because, as I understand it, it actually became too restrictive on this front). On the other hand, in something like Java, you need a lot of discipline to have pervasive immutability and you have to go out of your way to avoid using otherwise common constructs (e.g. if statements since those are statements rather than expressions) that require very kludgey workarounds (encoding an algebraic data type as a fold and using that in place of if statements). Kotlin is in the middle. If you don't use certain parts of the standard library and don't use things like var, you have pervasive immutability. At a language level this is quite similar to Scala (Scala has a more immutably focused standard library than Kotlin does).
- haspok 6y agoI'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.