5 ms·
I've written small stuff in Haskell a decade ago. I have a soft spot for the language -- it has clearly influenced many notable languages that came after it. Bu
by codeflo 5y ago
I've written small stuff in Haskell a decade ago. I have a soft spot for the language -- it has clearly influenced many notable languages that came after it. But I also admire the patience of anyone who actually manages to use it in practice, there are so many little papercuts that don't get resolved, basically for a decade or more. If I'm cynical, I'd say that's because little practical stuff is often not worth publishing papers about.
Error handling was, for me, a big one. For a functional language, Haskell seems very obsessed with exceptions. Even supposedly pure stuff, like "head" (first element of a list) throws an exception if the list is empty. You'd think Haskell would be the first language to have it return a Maybe value, but no. (Rust, BTW, gets functions like this right; they all return an Option.)
This reliance on exceptions clashes hard with the functional paradigm. Exceptions are "magic": They are special additional values that any type can have (so an Int can either be an actual integer or an exception value), but you can't test for them or handle them in any way pure code, you need IO for that. Which the language makes intentionally hard to use, that's Haskell's entire thing.
- exdsq 5y agoIn Haskell you’d use pattern matching guards for the empty list which works better for recursion & doesn’t require you to handle the Maybe monad in primitive data structures. Even though Monads were introduced to programming after Haskell had been written (to deal with IO, SPJ and Wadler have a good paper on this) I don’t know if this would have been worth changing. After all, you can always wrap a custom Maybe<List> if you need it!
- codeflo 5y agoTrue, but I think enabling more cases where you can use function composition instead of pattern matching and explicit recursion would be a win.
- dundarious 5y agoOP is saying the existence of head means someone will use it and get the paper cut. It's true you just shouldn't use it (even when you know it's non-empty, write the throw yourself). But that's why it's an annoyance. Arguably it's even more of a problem, it's a "foot gun". It would be nice if the Prelude was just replaced, but that obviously presents a host of annoying challenges. Several alternative Preludes exist, but none appear to be becoming the new center of mass.
- madsbuch 5y agoWhat you are running into is the pureness of Haskell. The `head` function in Haskell is only partially defined. What you see as an exception is a case where a function is not defined. This is all by intent. defining a `head :: [a] -> Maybe a` is a very simple matter and definitely something a developer should be encouraged instead of using the prelude. exceptions in Haskell are not meant to be used as a first class thing, but is the way to ensure a full Turing complete language where it is possible to define non-terminating behavior. Hence it really is by design.
- dundarious 5y agoThe issue as I see it, is that one of the main selling points of a pure language like Haskell, is that you have to explicitly state where a certain class of surprises/failures (from IO) lie, and therefore, you can account for them better, handle them cleanly, prevent them from arising accidentally or in some ways maliciously, etc. Partial functions are another kind of surprise/failure, but they are not at all explicit. This is a bit strange. It's like caring deeply about whether printf fails, but not so much whether array indexing is out of bounds. Haskell has a great story for both kinds of issue, and even its exceptions are better than panics IMO, even if they are about as tricky to use as POSIX signals, but it is relatively obscure and stigmatized to do a gross thing like use unsafePerformIO, but actually quite common/natural and accepted to use head. Lots and lots of people know to do the right thing for the latter, and there is something of a community push to avoid them, but it's just interesting to note how easy it is to make one mistake versus the other, when both matter a lot. One is treated as fundamental, and the other is not, but day to day, both kinds of issue lead to a similar magnitude of headaches, so the disparity is noteworthy. I'd love it if even just the type signature recorded that exceptions are possible, even if there is no practical effect on how or where it is used.
- Tainnor 5y agoIdris does that. If you add "%default total" to a file (or the equivalent compiler flag), it will make sure every function terminates unless it's annotated with "partial". In the best case, only your main function and a couple others need to be partial.
- youerbt 5y ago> But I also admire the patience of anyone who actually manages to use it in practice Nothing you point out gets even close, in my mind, to stuff like null pointers or untyped code. So I wonder what languages you have in mind that require less patience. > you need IO for that. Which the language makes intentionally hard to use Well, that is simply not true.
- deleted 5y ago[deleted]
- caente 5y ago>Nothing you point out gets even close, in my mind, to stuff like null pointers or untyped code. So I wonder what languages you have in mind that require less patience. You two are talking about two different things: - The parent is talking about the ecosystem, how menial tasks have tooling in "less interesting" languages - You are talking about the language itself I would venture to guess that the parent would agree with you, if talking about the language in a vacuum. An interesting competition would be to develop a complex product, without external dependencies. My sad guess is that languages that are filled with escape hatches, like Java, Javascript, or python, would defeat more strict languages. It's a sad guess, because I actually do prefer the Haskell way.
- smegma2 5y ago> Well, that is simply not true. I think it's true, for example catch [1] requires IO. Do you mean with unsafePerformIO, or something else? 1: https://hackage.haskell.org/package/base-4.16.0.0/docs/Control-Exception.html#v:catch https://hackage.haskell.org/package/base-4.16.0.0/docs/Contr...
- odyssey7 5y agoThis is why I see PureScript as a better starting point. It was modeled after Haskell, but since it was created in 2013, many of the design choices were to avoid these sorts of things.
- ParetoOptimal 5y ago> But I also admire the patience of anyone who actually manages to use it in practice, If I'm cynical, I'd say that's because little practical stuff is often not worth publishing papers about. I feel my impatience pushes me towards Haskell if anything... local-reasoning for instance rather than "understand this entire call chain" requires less patience and is easier to get right. > there are so many little papercuts that don't get resolved, basically for a decade or more. I've been using Haskell a decade in practice, can you tell me what papercuts you had in mind? I'm assuming I and other real world Haskellers might just see them as much less of a priority all things considered, but I'd like to be sure I'm not missing something.
- zeepthee 5y ago
- Tainnor 5y agoPartial functions and exceptions are a compromise solution for the fact that you sometimes do know more than the compiler does. I think it's fine to throw an exception in the case of "programmer error". It's the equivalent of assertions in other languages. Yes, it can blow up, but at least the error is a bit more localised. Having head return a Maybe means that you'll have to awkwardly handly a Nothing case even in situations where there is no sane behaviour to be added because it just simply would make no sense for a particular list to be empty unless you've introduced a bug somewhere else. It's hard to "recover" from such an error. The same goes for e.g. division, which is partial too (can't divide by 0), but having it return Maybe would make arithmetic incredibly awkward. You could instead define e.g. x/0=0 or any other value—some languages like Coq or Pony do that, but I think that has the drawback that this makes it rather easy to mask some ugly errors. In many such cases, the Haskell type system (without advanced extensions) is not expressive enough to encode everything you know about your values. In a language with dependent types, such as Idris, you can specify the length of the list in your type; then you can have a type-safe, total head function that doesn't return Maybe. You can also write a division function that requires a proof (possibly implicit) that the denominator is not zero. But dependently typed languages are much more niche than Haskell.
- deleted 5y ago[deleted]
- bspammer 5y agoHaskell has had non-empty lists as a type for a long time: https://hackage.haskell.org/package/base-4.16.0.0/docs/Data-List-NonEmpty.html https://hackage.haskell.org/package/base-4.16.0.0/docs/Data-... Having partial functions in the Prelude is, as far as I know, widely regarded as a mistake and they are only kept around for backwards compatibility. Anyone writing code nowadays should be using safeHead or non-empty lists.
- oats 5y agoOr pattern matching that takes account of the empty list case. Orrrrr using a fold! I usually find when I start matching on list values that the function could be better expressed with a fold instead.
- PragmaticPulp 5y ago> But I also admire the patience of anyone who actually manages to use it in practice, there are so many little papercuts that don't get resolved, basically for a decade or more. I Great summary of what it’s like to use any niche language. You don’t realize the value of a mature and highly used ecosystem until you have to chase issues in an ecosystem where maybe 5 other people total are doing the same thing you’re doing and nobody has updated some library you need for 3 years. Fun for hobbies, terrible for real work.
- toomanydoubts 5y agoThere are some alternative Preludes that attempt to fix this, bringing a safer std lib to the table.