3 ms·
> * Laziness and higher-kinded types are both features with costs that significantly outweigh their benefits. > It sounds like you disagree with the last bulle
by zenhack 7y ago
> * Laziness and higher-kinded types are both features with costs that significantly outweigh their benefits.
> It sounds like you disagree with the last bullet point. If so, then either we've had different experiences or we walked away with different conclusions from them.
I more or less agree on laziness (at least lazy-by-default; having a lazy type as found in OCaml available is a big win for little downside).
Re: Higher-kinded types: I'm curious as to what you think the high costs are? My impression is that they've mostly been left out of Elm due to pedagogical concerns. Is it just that or are there other things?
- rtfeldman 7y agoLearning curve is a very high cost by itself; HKP is the reason Haskell is notoriously difficult to learn. (Compare to Elm, which is also a referentially transparent typed ML with parameteric polymorohism and row types, with nearly identical syntax to Haskell...and which is notoriously easy to learn.) Haskell beginners don't need to get into GADTs and type families and higher-ranked types, but HKP is practically unavoidable on the road to understanding a Haskell program that prints Hello World. Another cost is in standard library complexity. You can't have HKP and not have a stdlib with Functor/Monoid/Monad etc. As Scala has demonstrated, if you have HKP but don't put these in the stdlib, a large faction will emerge pushing an alternative stdlib that has them. A larger, more complex stdlib is a cost, and so is a fractured community and ecosystem; HKP means you'll have to pick one of those two. API design is another. Without HKP you write a function that takes a List. With HKP you now need to decide: should it actually take a List, or is it better to take a Functor/Semigroup/Monoid/Applicative/Monad instead? If you choose one of the more generic ones, now it takes more mental steps to collapse the indirection when reading it. (1. I have a Maybe Int. 2. This function expects an `s`. 3. `s` is constrained by `Semigroup s`. 4. Can I pass a Maybe Int to something expecting a Semigroup? Compare to "This function takes a `Maybe a`", and multiply that small delta of effort by a massive coefficient; this is something everyone who reads these types will do many, many times.) This indirection also has implementation costs; in theory you could make docs and error messages about as nice if HKP is involved as if not, but there's an implementation cost there, and it seems like it must be pretty steep if you stack up languages with HKP and their quality of error messages and docs against other typed languages that don't. So I'd say it's one huge cost (automatic induction into the highest tier of learning curve steepness), one big cost (either a larger and more complex stdlib or fractured community), and several smaller costs with high coefficients because they come up extremely often. Yeah there are benefits too, but I don't think they get anywhere near outweighing the costs.
- sullyj3 7y agoThe point about how difficult it is to learn is well made and well taken. This difficulty is very contextual though - this is a problem for the language in terms of adoption, but it's not a problem if, for instance, you're a team of experienced Haskell programmers deciding on which language to use for your Important Business Applications. I think that you overstate the cognitive overhead of reading polymorphic type signatures to those who are reasonably familiar with common idioms. Taking a second to remember that `Maybe a` is a semigroup if its argument is seems like a small cost to pay to me. I think there's a significant benefit to highly polymorphic functions in the standard library which seems to rarely be brought up. Polymorphic functions are applicable more often. So then if the function is already written for you, and you use it, then anyone who reads your code has to look at one less definition to understand it (if they're familiar with the library function). This forms a larger common vocabulary and in some ways imposes a smaller cognitive overhead on the reader. Speaking more broadly, people are often frustrated by the number of abstractions from the standard library that they have to learn to be productive. But they don't notice all of the abstractions they now don't have to learn in individual codebases - because they don't have to exist. And this effect adds up - every time there would have been a slightly less well implemented, proprietary, monomorphic version of a function in a codebase and you use the polymorphic standard library one instead, everyone who reads that code has one less thing to get their head around. It pays for itself. I also feel like you understate the benefits - the amount of times I think for 20 seconds and realize that the complicated function I was about to write is just like, `traverse` or something is incredible to me. I think it's possible to to have legitimate disagreements here, which are often driven by differing personal experiences, and by the kinds of domains people are operating in and so on. I don't think there's a one size fits all answer.
- sullyj3 7y agoTo indulge in fisking a tiny bit - > HKP is practically unavoidable on the road to understanding a Haskell program that prints Hello World. main :: IO () main = putStrLn "Hello World" Doesn't require an understanding of HKP at all. There isn't even any LKP. I do agree that it's necessary for a productive employed Haskeller, but not a beginner playing around with simple command line apps. There's a distinction to be made between understanding enough to make it run (Just label the IO bits with IO and think of 'do' as kind of like imperative programming but not really) and understanding more deeply, which only becomes necessary later. > With HKP you now need to decide: should it actually take a List, or is it better to take a Functor/Semigroup/Monoid/Applicative/Monad instead? It doesn't seem like a decision that would involves a lot of cognitive overhead. In general I'd probably just go with whatever type GHC infers. Failing that, it kind of arises naturally from like, what the function is about. Is it about reducing the List to a single value? Use Foldable. Is it about transforming the elements of the list? Use Functor. Is it about nondeterminism, but the code isn't necessarily specific to that computational context? Use Applicative if there are no sequential dependencies (which the type checker knows anyway) and Monad otherwise. Ok, maybe it seems a little complex when you write it out, but it's really fairly instinctual.