Hacker News new | past | comments | ask | show | jobs | submit login
Building a Better Custom Haskell Prelude (stephendiehl.com)
96 points by ingve on April 8, 2016 | hide | past | favorite | 44 comments



Haskell desperately needs a thorough revamping of its Prelude, and it really has to become part of an updated language standard document. Too much of the burden of learning Haskell consists of figuring out how to dodge the pitfalls in the current Prelude. The arguments against revamping basically boil down to "but that would obsolete this old textbook!" and that just doesn't wash.

Getting this job done right is essential to Haskell becoming a "hundred-year language".


Some specific things that I do in Haskell programs written for production:

1. Define a FutureProofing.hs module that anticipates changes to GHC that are, or now were, inevitable, such as Monad becoming a subclass of Applicative, and "fail" moving out of Monad. I redefine MonadIO to depend on Applicative so that I can use "pure" universally, hiding "return" from Prelude.

2. I have a longish list of things I hide from the Prelude, besides "return" (above). I haven't yet crossed the Rubicon of inhibiting the standard Prelude altogether, but I don't allow myself to use the bad parts.

3. I have a GenericIO.hs module that defines a typeclass that allows me to use any type of string for pathnames or data. This eliminates nearly all boilerplate conversions when dealing with the IO library and anticipates the changes that might eventually come to the standard Prelude.

In short, I expect and anticipate the standard library to improve, and will not have to do much work to exploit those improvements when they do arrive. I steer clear of the pitfalls. And unlike the OP, I try to avoid complicating the problem by adding new things to my universal imports; I don't mind having longish import lists at the heads of my modules (and I always enumerate all the names that I import).


If you're still using string types for pathnames you may want to check out the path and path-io packages. The path package defines a Path type parameterized by its locality (absolute or relative) and type (pointing at a file or a directory).

Especially the distinction between relative and absolute paths adds a nice safety layer.


> The arguments against revamping basically boil down to "but that would obsolete this old textbook!"

The biggest argument against revamping is breaking tons of existing code. Haskell has been trying to gain traction in industry and randomly breaking existing code is a good way to have people lose faith. Now they have locked themselves into roughly "no breaking changes for three years after initial release".

Luckily there has recently been some more progress caused by realizing that online repositories are a great way to validate how much of a breaking change would happen with hypothetical changes, for instance removing the pure/return duality.


People can (relatively) easily create a Haskell2016 standard with a new default Prelude, and keep the old Prelude by default on Haskell2010 code. Add a compiler switch to set it manually, and it's as trouble-free as it can be.

It is great that Cabal pushes people to declare what version of the language they are using.

I guess the reason it was still not done is because people do not agree on what the new Prelude should be, if should be a Prelude at all, or if it should be a single one.


I'm not saying that the current Prelude should become unavailable. That would be really stupid.


Have the Haskell gurus considered doing a Python 3-style update/fork hybrid the explicit purpose of code-breaking changes? Haskell 2?


The discussion is about cleaning up the standard library, not the language. Multiple standard libraries can coexist happily.


It's interesting how different people's preludes are. We have a custom prelude at work, and things that it has that this one doesn't:

* Time types and functions.

* Overloaded (.) and `id`, and some arrow functionality. * More from 'safe'.

* Semigroup.

* String conversions from 'string-conversions', type synonyms for both lazy and strict Text and ByteString types.

* Vector and UUID.

What we don't have:

* The monad transformers by default, although maybe we should.

* Generics. Again maybe we should.

* Bits, Complex.

* All the custom implemented improvements.

* Semiring, DeepSeq, the containers types, concurrency.

Basically our prelude is more conservative, I guess, although it identifies and solves a lot of the same problems. Perhaps that is some common ground to start doing small improvements to the "real" prelude.


There have been numerous attempts at making a better Haskell prelude. Basically basic functions like getting the head of a list can have a runtime error (for example, the list is empty). That's unnecessary in Haskell (could return a `Maybe a`). Stephen Diehl is the guy to do this right. I've been incredulous of other replacement preludes in the past but I may use this one.


Exactly. Many people do not know how hard it is to improve on the current prelude, even though it's entirely possible. Most decisions and tradeoffs in Haskell weren't just done on a whim, there is an ideology and reasoning behind it (I am not sure if I want a maybe monad as a possible result of my list operations, though). My first thought when I read that headline was "Wow, good luck with that."

I do not know Stephen Diehl but I hope you're right about his capabilities, it would definitely be exciting to see that in Haskell!


To my understanding Haskell prelude is unsafe because Haskell sometime sacrifices usability/friendliness for satisfying certain theorems.

http://stackoverflow.com/questions/6364409/why-does-haskells...


Satisfying theorems is generally for the purpose of friendliness, by ensuring consistency. In this case, they had two options:

(a) be consistent as if the language had type-level literals, which resulted in nicer code in the usual case, but some unsafety on unchecked edge cases, or

(b) give more safety, but force everyone to check for the empty list even when they don't need to.

They went with (a) - it's the type of decision Haskell is (I'd argue incorrectly) criticized for not doing - people complain about having to prove things to their compiler. Of course, in hindsight, and given the language ecosystem now (and that option (b) is instead consistent with all the monad-centric libraries, etc.), (b) seems like a better choice. Which one is better could just as easily change again if empty lists became distinguished at the type-level in Prelude in the future. This is also something that would either crash, or be similarly inconsistent (just return null, causing eventual crashes instead) in any other language. It's notable, though, that the community is making effort to fix these tiny inconsistencies, rather than punting on it for backwards compatibility, as so many other ecosystems do.


Partly. Tsuyoshi Ito's answer on that page is IMO better than the original: `head` mattered at a time when pattern matching didn't exist; but today the only reason to use `head` is to form some sort of pointfree expression operating on lists that you've already filtered to be not-null. That is, you're doing `map (transform . head) . takeWhile (not . null)` or something, or maybe `takeWhile` is replaced with `filter` or so. If you cannot assert that the list is non-null then you have to handle two branches, and this code will generally look clearer if you pull it into a `where` clause and write it with pattern matching rather than with an adjusted `head` followed immediately by an appropriate `maybe` function.


With `[a] -> Maybe a`, it's not so bad if you use `catMaybes` from Data.Maybe.

map transform . catMaybes . map safeHead


Even better with mapMaybe:

    map transform . mapMaybe safeHead


In other words, it's this way because people want to emulate an unfriendly language, used on another domain where terseness is even more important than in Haskell code.

That does not make it a good choice. Just a kind of default, but it's a bad default.


isn't that just saying that 'head' shouldn't return [] on an empty list, because [] has a type which is never the type of what 'head' should return? that's separate from why 'head's type isn't [a] -> Maybe a.


I have found Haskell just has so many of these "paper cuts" that I have just walked away.


I agree, but I've fallen in love with Purescript[0] recently instead, and its made it easier to ease into Haskell. Although to be honest, I prefer Purescript as a language!

[0] http://www.purescript.org/


I could see that. I find that Haskell specifically is very hard to ease into, whereas a lot of similar languages are much easier. For me, I wasn't comfortable with my Haskell skills until I learned Idris, which is arguably a more difficult language -- ironically, I found the Idris documentation to be a lot more user friendly than Haskell's just because it comes from the official team (and I'm sure that Purescript is like this too). For anyone struggling to learn Haskell, I'd probably recommend a different purely functional language first (at least until haskellbook comes out).


That's because PureScript is what Haskell would be if it were written in the last 2-3 years starting from a clean slate. It doesn't have to deal with legacy code.


That's really a sad thing to hear, because for the experienced the benefits brought about by Haskell is so plainly obvious that it's obviously worth the trouble to navigate the mine of the Prelude full of legacy cruft.


My #1 issue was I work at several sites and at home. I don't really use a laptop so I just use git as a single user. The package management was such that I could never have the same environment in multiple of locations. Something would break and I spent more time fixing then doing.


That was a problem commonly called 'cabal hell'. You can alternatively use the stack build tool, https://github.com/commercialhaskell/stack, which works by fixing package versions against stackage snapshots.

Before using stack I was always afraid to come back to a Haskell project after a few months, since using stack that's not a problem. Also, you don't have to worry about different machines, because stack is ensuring that you have the pretty much same build environment (at least the Haskell packages will have the exact same version).

Stack also recently added support for using Nix to solve the problem fixing non-Haskell dependencies for a project.


Well after the love affair I have with Racket wears off I just might try to start back up in Haskell. Thanks! Love the idea of using nix.


This looks so much better than Cabal.

Why have I never heard about it in all the guides and StackOverflow answers I've had to search through when trying to escape Cabal hell?


The project has existed only since last summer.


mcbuilder has already mentioned it, but I really want to emphasize that dependency management in Haskell is an entirely different story now with stack.


What about classy-prelude? I was thinking about using that one.. (based on Stephen's advice in the other blog post http://www.stephendiehl.com/posts/production.html)

Has anyone experience with it, pros, cons?


classy-prelude is a "Big Vehicle Prelude", that is, a Mahayana Prelude.


It's not! classy-prelude is compatible with existing abstractions and doesn't try to replace large portions of the basic types and classes.


What does this mean?


"Mahayana" is a Sanskrit word composed of "maha" for "great" and "yana" for "vehicle." It's used to describe a diverse tradition of Buddhism that somehow (the history is not exactly clear) diverged from that monastic consensus which became the Theravada ("way of the elders") tradition...

Mahayana sees itself as going beyond what they might call "individual" or "personal" liberation, in that it sets up a grand goal of liberating all beings. Practitioners hold this ambition in mind, and see the "Hinayana" (small vehicle; a pejorative term used in Mahayana rhetoric to denounce other interpretations of Buddhism) ambition as limited or as merely a first step towards the ultimate goal of universal liberation.

How this relates to the Haskell prelude, I'm not so sure... but yeah, I suppose a "Mahayana prelude" is more grandly ambitious and therefore also more expansive and difficult to realize...

(By the way, in religious studies, Rupert Gethin's book The Foundations of Buddhism has an introductory chapter about Mahayana, and an in depth review is Paul Williams's Mahayana Buddhism: the Doctrinal Foundations.)


So the terms are coming from the Buddhist tradition, where there is a category of Buddhist denominations usually (a) valuing the Lotus Sutra (as sort of a central uniting point and/or shibboleth); (b) believing in celestial buddhas and bodhisattvas who you venerate and whose assistance you request in life; (c) valuing the idea of us all being "on the path to nirvana" (the "bodhisattva" ideal) even if we are not monks; and (d) representing the diversity of Buddhist denominations as largely being "lesser vehicles" (hinayana) to nirvana, and their own category as being a "greater vehicle" (mahayana) to get there. "Maha-" is a prefix meaning "big," but often also connoting "great, impressive, respectable, noble".

In practical terms a "big-vehicle Prelude" is saying "I want to do something much smarter than the original Prelude and I am willing to replace most of the underlying abstractions in the Prelude to do it," which can sometimes mess with code that has to deal with old-prelude and new-prelude subsystems of code. A "small-vehicle Prelude" is much more "I have a couple extra modules here and there that I want to import, and maybe I want to fix a couple of these operators to a slightly more general definition with a typeclass, but largely I want everything to be the same, hence intercompatible with minimum fuss."


Sorry, dumb joke. Mahayana Buddhism is also known as "big vehicle" or "great vehicle" Buddhism.


Yeah I got that from Wikipedia, I just don't know what you mean by describing it as "big vehicle". Like it's bloated, or too broad or something?


FTA:

"Big Vehicle Prelude - Fix most of the deficiencies in the Prelude by introducing new abstractions that replace large portions of the basic types and class, and generally redefine the way we write Haskell. See numeric-prelude.

Small Vehicle Prelude - Fix the broken parts of the Prelude by building on existing types and classes and masking broken bits. See basic-prelude."


Oh oops, I guess I skimmed over the intro


Stephen Diehl is a good example of a content creator that a lot of people would love to send their money to :-)


> The Haskell Prelude is the default import into all Haskell modules, it provides an endless number of ways to shoot ourselves in the foot and historical cruft that can’t be removed.

As someone who's only recently started using Haskell seriously, what are some items from this "endless" list of things? Coming from a Standard ML background, one thing about Haskell that I prefer is that the Prelude is so powerful on it's own; SML's set of top-level functions is pretty limited.


Yep, same here coming from OCaml. MLs seem a lot more conservative with their standard libraries. Actually, with OCaml there have been a ton of attempts at making the standard library larger (Extlib, Batteries, Containers, Core, etc.) which is pretty much the opposite of what people are doing with Haskell. I always end up midsing Prelude when I'm working in OCaml, so it feels weird to me that people would want to strip it down so much.


I think the prevalence of articles like this one, about what's wrong with the Prelude, are exactly the reason for the impulse not to have such a big stdlib. If Haskell's were more modularised, then there wouldn't be this perpetual dissatisfaction with it (although, of course, there might be different dissatisfactions); you could just slice'n'dice it into the shape you wanted.


What are people's thoughts about using constraint kinds in the prelude, so you can make things with class constraints monads? Like making a Set monad.

This stack overflow answer shows what I'm talking about http://stackoverflow.com/a/22314189/1080531




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: