3 ms·
Sum types, not having null in every type, multiple dispatch (useful for numbers) and higher-kinded polymorphism (e.g. X can also be a type parameter in X[T]) ar
by willtim 8y ago
Sum types, not having null in every type, multiple dispatch (useful for numbers) and higher-kinded polymorphism (e.g. X can also be a type parameter in X[T]) are at least four very useful features that Haskell has and Java doesn't. Haskell also has excellent type inference that makes it as succinct as Python despite the static types.
In practice, despite the features Java can support, it's typically too much work to define a new type for safety reasons. Strings are used everywhere, instead of more domain specific types. Even the designers made compareTo return an Int!
- btschaegg 8y agoSo much this. I'm not actually that much of a fan of Haskell (might be due to a bad introduction to it), but dabbling in F#, I'm constantly amazed by how much more robust (and self-describing) you can design your APIs while still being way more concise than when using Java or C#. Since Haskell's type system is still more powerful than what is achievable with the .NET CLR, I'd assume this effect to be even stronger there. It's really amazing how far sane language defaults can get you. In C#, I encounter badly designed types all the time. The main reason for this is not that it is impossible to design them right, but that the language makes it so tedious. I'm also really longing for the new features in C# 8, in the hope that they manage to remedy some of that (record types, pattern matching and non-nullable references sound like a good start).
- platz 8y agoAs a c# person, Sadly the pattern matching will not do exhaustiveness checking which makes adding new data to cases much more error prone than without it, so there is less incentive to move away from inheritance
- akra 8y agoI suspect there is a point of diminishing returns with this though; the extra complexity in the type system gets you less and less benefit but at a greater cost to code complexity/comprehension. It could explain why the mixed Scala/F# paradigm has more industry usage than Haskell right now. i.e. I am trading a type system that has a little more expressiveness for a large amount of libraries, ecosystem, rock-solid VM, testing, corporate buy-in, etc. In a .NET shop for example its much easier to just drop an F# DLL into your codebase (in VS create a new F# project in a few clicks); there's no need to change anything else (e.g. build machines, build tooling, testing tools) since it's all IL anyway; same goes with Scala and Java. My experience in coding functionally has shown that mutation (especially if localized and doesn't leak outside of a function) can really help functional code become a lot faster. From what you describe C# is slowly becoming an F# clone; which may be a good thing or not. Still think that if you need these features you should move to Scala/F#/Ocaml/Haskell though since I suspect these features will be added to C# in a clumsy/compromise way given its roots (e.g Scala and F# already have exhaustive pattern matching, async streams/iterators, better type inference, etc.).
- btschaegg 8y ago> Still think that if you need these features you should move to Scala/F#/Ocaml/Haskell though since I suspect these features will be added to C# in a clumsy/compromise way given its roots 100% agreed, with one caveat: You usually don't get to go "Boss, we're using F# now, 'kay?" Some workplaces are sadly very constricted in those terms. Maybe I'll see the day when my team gets to use a more functional language, but until then, I'll take what I can get.
- unhammer 8y ago> mutation (especially if localized and doesn't leak outside of a function) can really help functional code become a lot faster There's this magical thing in Haskell called the "ST Monad", where you can have a pure function (does not have IO in its type signature, does not use unsafePerformIO) that takes a normal value, returns another value, but can use mutation inside the function, and the type system guarantees that the mutation doesn't "leave" the function. So for those cases where, in C++ I would think "this function is pure enough, it doesn't print or order fish food, though it does mutate this temporary array", Haskell's compiler will actually confirm for you "yeah, you're right, it is pure enough".