Hacker News new | ask | show | jobs
by codeflo 984 days ago
> Personally, I think Haskell is approximately stuck in a local maximum, which can only be escaped by embracing dependent types

To give a counter point, there seem to be lots of small wins that aren't taken, possibly in part because people seem to wait for the big ideas.

Take partial functions like head: You can do what Haskell and Java do and throw an exception. Or you can have dependent types and statically prevent the function from being applied to an empty list. But the obvious and easy solution in the current language would be to return Maybe, which isn't done because there's a feeling that it's not a big enough step to be worth the effort, and dependent types will eventually solve this anyway.

6 comments

> But the obvious and easy solution in the current language would be to return Maybe, which isn't done because there's a feeling that it's not a big enough step to be worth the effort, and dependent types will eventually solve this anyway.

That's not why it's not done. listToMaybe already exists[1] and you can't change the type of head without breaking everyone's code, so head in the next version of base will come with a warning[2] and that's about as much as you can do whilst still maintaining backwards compatibility.

[1] https://www.stackage.org/haddock/lts-21.14/base-4.17.2.0/Dat...

[2] https://github.com/haskell/core-libraries-committee/issues/8...

That's an interesting counterpoint, but I think it's a bit of a different tradeoff. Adding, say, dependent types, can be done as a gradual process with opt-in (via language extensions) and "only" requires heroic effort on the part of the compiler writers... whereas changing 'head' requires 10000i Hackage packages to update their code.

This is all tied into how inflexible the base/Prelude story is currently, etc. If the Prelude were a fully independent library where you could just depend on any old version you like, then there'd be no problem changing the signature. (Other than the usual diamond dependency problems). Of course you can choose NoPrelude and go from there, but then you're already in a place where changing 'head' doesn't matter to you, only the maintainer of your alternative Prelude.

People are working on both of these aspects, and that's got me really excited about where Haskell is going these days!

(I've always loved Haskell as a language, but there have been undeniable ecosystem issues.)

In cases when head is used though, you clearly just want to unpack the first element of a list. So if you replace head with a function that returns a Maybe, all you’re going to do is pattern match on its result to check if it’s a Just. So I don’t see a benefit over matching x::_ against the list straight away.

(I do see how a function like that is useful, but I don’t think it’s a good replacement for head.)

> all you’re going to do is pattern match on its result to check if it’s a Just

Something is eventually going to pattern match on it. But it might well be some handler way way up at the top of my program, and all I want to do right now is `map` and `bind` as normal.

Then you're clearly not using head. For this you can use listToMaybe.

Don't change the meaning of an identifier to something meant for a completely different use. The alternative change involving dependent types or liquid types doesn't break existing code. Changing head so that it returns a Maybe breaks code, because its use case is different.

I'm not using head, because I know Haskell well. But someone new to the language is never, ever, ever going to intuit that you get the head of a list with `listToMaybe` and `head` actually means `unsafeHead` and should almost never be used.

> Changing head so that it returns a Maybe breaks code

Of course. So did `Applicative`. So did `Foldable` and `Traversable`. So will removing (/=) from Eq, whenever that actually gets merged - and that was imo a far, far less problematic wart than having partial functions in Prelude. Breaking changes need to be made very carefully and very slowly: simplified subsumption was terribly handled, if not an outright mistake. But refusing to make them at all is how you get C++.

> But someone new to the language is never, ever, ever going to intuit that you get the head of a list with `listToMaybe`

They don't have to. Pattern matching is one of the first things they will learn, so they can use that instead.

> and `head` actually means `unsafeHead` and should almost never be used.

They don't need to intuit it. The next version of base will have a warning on `head` telling them to use a safer alternative instead.

By the way, the Haskell community is currently struggling to accept that it should make fewer breaking changes, in order to become a more stable platform for industry use. We're definitely not going to start breaking more code!

Some people think that if you don't throw exceptions then you don't have to bother dealing with errors. ¯\_(ツ)_/¯
Liquid Haskell solves the head problem as well as DTs - and it's available as a plugin. You can literally use Liquid types as easily as any library.
I recently got into an argument with someone who insisted that since “undefined” could be considered “non-terminating” there was nothing wrong with the current behaviour.
I guess parsing the list into a NonEmpty would probably be considered somewhat more idiomatic haskell than returning a Maybe from head.