Why are Partial Functions so prevalent in Prelude?

Others have hinted at the direct answers to your question and provided some context and further reading. To put it plainly, though, base is full of partial functions because it’s around 35 years old and comes out of a tradition that’s even older than that. A lot of ideas have improved since then. The language you are using for comparison, Rust, is almost 20 years younger, and got to pick from a lot of the better ideas that had developed.

But the good ideas exist now—why does Haskell still use the “bad” ones? (Leaving aside the legitimate value of partial functions and all side conversations there.) Well, Rust could jump straight into a sounder standard library because they didn’t have to worry about breaking backward compatibility. There was no Rust code to be compatible with. Haskell, meanwhile, has to slowly navigate through the compatibility story. It’s a matter of maturity. I’m sure the good ideas that are developed in the next 15 years’ time will eventually take just as long to filter into Rust as it matures and continues to have widespread use. See also C++, which is about as old as Haskell and also has to deal with a bunch of backward compatibility. But just like C++, Haskell is also evolving. From a long-term perspective, you might say that Haskell is just in a temporary, vestigial position with respect to the prevalence of partial functions in base.

A call to arms: issues that affect newcomers like patchy documentation are the kind that rely on community support to fix. In a commercial setting, these kinds of problems get papered over quickly when experienced programmers (and an existing codebase) are on hand to provide mentorship. There’s no incentive to spend time fixing the paper cuts when the immediate goal is to build features for customers. Academics have no direct incentive, either. (Teachers do, I suppose, but they also have the competing incentive of keeping their books and materials up-to-date.) And unlike most popular languages, Haskell has no central benefactor with an incentive to improve their influence by improving the fundamentals of the language. So if the state of Prelude bothers you, go fix it!! :slight_smile:

9 Likes

You raise some interesting questions @AntC2

In rust division by zero for integers results in a panic by default. However the compiler will attempt to detect this in code:

int_value / 0     ● this operation will panic at runtime  `#[deny(unconditional_panic)]` on by default

For integer division you have checked_div which returns Option<i8> for signed 8bit integers, Option<i16> for signed 16 bit integers, etc.

Finally, division by zero for floats results in Infinity which can be pattern matched against using std::f64::INFINITY

In rust this will result in a panic when the code is compiled / ran in development mode and will cause overflow in production mode. Yet again the compiler will try its best to determine this before runtime.

I’m not so sure about other languages since my experience with ML based languages is basically null. In rust code is not lazily evaluated.

I heavily disagree here. It’s one thing for you to introduce a dependency written by a third party, and for that dependency to be poorly built (i.e. inconsistent documentation on what causes exceptions at runtime and a lack of Maybe) vs the standard library of the language itself behaving in such a fashion.

I don’t believe that making Haskell safer by default would be a mark against its academic roots or make it less expressive.

I highly recommend you read the official rust book. I have not found such a high quality piece of learning material for any other language that’s officially endorsed by the foundation backing that language. The reason I mention the rust book is because it does not shy away from concepts like panicking and rust’s Option and Result types. I don’t believe making a language safer (either through parsing vs validating as mentioned above) makes it significantly harder to learn.

2 Likes

I just wanted to say thank you for everyone for the detailed answers as well as context provided. I’m planning on reading through A History of Haskell.

Personally, I’m excited to checkout the safe package, also after some further searching I also found out about Liquid Haskell, which I’ll try out when I have some time. I don’t agree with the mind set that it’s OK to just accept partial functions floating around in the ether and that “You’ll barely run into them” - so I’ll see how I can go about configuring Haskell in a way that’s both expressive and practical from my perspective.

3 Likes

GHC does not support arbitrary compilation-time code execution (except for Template Haskell). Just like with base this is not going to get solved unless several people step up and invest five years of their time into solving this particular issue.

This also results in a far more annoying problem: literal overloading typeclasses (Num, Fractional, IsString and IsList) all are literal -> a functions that are forced to throw exceptions at runtime to be useful.

2 Likes

I love safe, have been relying on it for most of my Haskell career. Would love to have it all in Prelude.

3 Likes

even if you start with an n with some proof p, you often need to then prove q. Haskell just can’t do this at the moment.

Ghosts of Departed Proofs is a step in this direction. It requires a little contortion at both the value and type levels, but it’s quite general (for example, it’s the approach underlying justified-containers). The author, Matt Noonan, positions it as an alternative to partial functions and failure conditions (Maybe/ Either wrapping).

OP, I don’t necessarily recommend GDP for everyday use, but I do recommend reading the paper for a fascinating glimpse at just how much information you can pack into a type.

1 Like

If “safe” now also means “doesn’t ever crash”, then Haskell is the wrong language to use:

(…yes, alright: an uptime of 99.999% - 5 minutes in a year - isn’t exactly “doesn’t ever crash”, but it’s a reasonably-good approximation!)

Liquid Haskell looks intriguing, will take a look later.

Thanks!

1 Like

Thanks for the link! I’m glad you pointed me to it.

Nice, I didn’t know indeed.


I don’t think I read in this thread that using an alternative prelude could also be an option if this really bothers you.

I should mention that in OCaml, there’s this convention of suffixing throwing functions with _exn, to signal that the function may throw an exception.

So you could call List.hd_exn, while List.hd would return a maybe.

I find this convention rather nice, since it requires no technology and things are crystal clear to the reader.

Maybe that’s an idea to implement, at least in one’s own code.

2 Likes

I think I’ve seen this called “progressing”.

1 Like