Hacker News new | ask | show | jobs
by madsbuch 1571 days ago
What 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.

1 comments

The 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.

IO is not (primarily) about where failures lie, but about where side effects lie- side effects are where you start caring about the order of execution.

Array indexing failures, on the other hand, are not something you typically care about at quite that granularity- they're usually just bugs, not something to recover from except perhaps at a much higher level.

The parent comment lumps these kinds of failures in with non-termination, which in pure functions is also typically just a bug rather than a recoverable failure. And this one isn't something you can generally check for, either- with lazy evaluation, every type in a Haskell program by default includes a "bottom" value.

I think both choices were made for a similar reason- actually handling array bounds check failures everywhere is pointless tedium (and often better folded into the iteration itself), and actually handling possible non-termination by using a total language can also get pretty tedious. There are languages that do both, and they have their uses, but Haskell went a different direction.

You make a good point about IO, I forgot how it's also not great about errors (but isn't there are an IO monad with better error treatment? -- it has been several years...). I also agree about granularity and tedium, but that's orthogonal to whether exceptions are the best way to approach such errors, and I don't think they are. Even Go's approach of explicit if-return is not tedium to me, but there are even less tedious approaches, that still let you handle the handle-able errors and do some last-ditch cleanup or just panic on the unhandle-able ones like indexing errors.

The interesting thing about Haskell exceptions are the async ones and the ability to `throwTo`, but I never really had a use for that, so on the whole, that was a bit of an encumbrance too. It's like trying to write exception safe C++ -- tedious and easy to get wrong. I remember a fair few sections of Parallel and Concurrent Programming in Haskell that temporarily didn't handle exceptions correctly, and it often wasn't for pure pedagogical reasons. Great book though.

Idris 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.
> quite common/natural and accepted to use head

No it isn't, not at all.

What absurd slander.