Towards a prevalent alternative prelude?

I come from the world of web-dev, specifically using PHP and JS. Two quite different languages in that JS can really never be changed, without breaking old sites - as the developer has no control of what version of JS is used for their site. As such JS is full of weird issues that can’t ever be fixed, that are incredibly annoying (and a pain to teach). I often end up telling students the convoluted history of JS, as it’s the only way to explain some of the weird behaviours.

PHP, on the other hand, can make breaking changes. And they’ve started to use this to make the language better. As long as the developer can control the version of PHP that’s being used, they’re fully in control over whether they break their code or not.

Having worked with both languages a lot, I appreciate that PHP can improve and get rid of its baggage. It is gradually improving as a language in a way that JS never can.

It would be great if String wasn’t the default. It’s incredibly anti-newbie. It took me a year to realise I shouldn’t be using String. And then I had to spend a day updating my codebase to use Text.

So I’m all for Haskell X breaking things to make it easier to get started.

5 Likes

I have the feeling that is difficult to find an agreement about a new standard for a language like haskell that is developed in large parts by the community. Approaches in the past that went this direction have failed, e.g. Haskell platform, Haskell2020.

Why not start a competition in writing new core libraries, with the restriction to only depend on base? The authors should request features they need at the base library maintainers. This restriction would naturally evolve the base library which could define the new Haskell standard as an end result. This way everybody who puts some effort in writing a library could get some influence.

2 Likes

And then I had to spend a day updating my codebase to use Text.
@smallhadroncollider

This is exactly how I see these problems. The fear of breakage and the specter of Python 3 is unfounded. Yes breaking changes create busy work. Yes they can cause ecosystem ripple. However, the type system does so much work that the transition becomes mechanical.

The ecosystem has also been moving away from many of the historic issues in base. For example, network dropped String support in version 3. That had a ripple, it took time for downstream to catch up, but now the work is done.

7 Likes

Hi folks, thank you for working on Haskell improvements. As a relatively new user, the new GHC20x extensions and a new Prelude sounds like great news.

I’ve considered using relude a few times because it has better defaults, such as Control.Monad, Data.Either, and Data.Maybe. It also exposes the Text type, but it seems like I still have to register the text dependency to use the value. As @ChShersh mentioned, a “qualified reexports” would be very useful, and that could provide standard namespace names for things like lazy bytestring (e.g. should it be LBS or BSL?).

However I’m sticking to base because it is “official” and somewhat easier to use. Though I am looking forward a more modern Prelude being blessed by the community and build on that instead.

3 Likes

I think we can separate this into a few separate issues:

  • We need to fix broken stuff
  • We need to expand what’s available by default
  • We want less pain with upgrades and dependencies

I think we can immediately move forward on “fixing broken stuff.” base-5.0.0.0 can be a big breaking change, but even base-4.14 could have a lot of breaking changes too - it’s a major bump! I don’t see any disagreement on this point. No on is saying “Nah let’s keep foldl around, it’s fine.”

Some people want a more pared down standard library, and some people want a beefier standard library. Both have compelling points. The “default” workflow better be really good, and I think it makes sense to target it towards beginner-friendly applications (without having bad-for-production choices, either).

So, this suggests to me that we might want to figure out a better way to pick a standard library. base::Prelude is deeply privileged right now. Can we alter Haskell such that this privilege disappears, and it’s easy to have a Good Default + a minimal base for those that want it?

9 Likes

This is OK but end-applications, but terrible for libraries. They need libraries.

There’s many reasons for this, but one good one is portability. GHCJS is used plenty in production (and I think the long term plan around merging it into GHC will be a tremendous boon to Haskell in industry), but one wants very different batteries included when writing frontend code. Still though, lots of a the “abstract nonsense” fundamentals are to be shared, and that’s the whole point! It’s very important containers, kmettverse math stuff, not accidentally import things that would make them less portable than they need to be, as that would default the whole purpose of GHCJS (sharing code between backend and frontend).

We need to fix broken stuff

I do think this is the most important, the problem is less stuff not being imported by the front, but separating the wheat from the chaff. New uses navigate a minefield, and there’s little way to help them. Once we have curated collections of stuff, users shouldn’t mind browsing and index to find what they need to import if they can be confident whatever turns up is something worth using. That index could be as simple as the combined haddocks of a library collection.

2 Likes

What would happen to packages that import base like base == 4.*? I am grepping index-00 and I see lots of hits (some quite popular too — like hspec).

There seems to be a lot of confusion about the relationship between Prelude and the dual roles of base library. Let me try to lay out the land.

  1. Starting from the lowest level, there is base-of-GHC-primitives, a collection of low-level modules that are provided by GHC and cannot be implemented anywhere else.

  2. Then there is base-the-kitchen-sink, which is a collection of modules that you get as part of library called base, guaranteed to always be available without fiddling with Cabal or Stack.

  3. Finally there’s Prelude, a special module that is always implicitly imported.

The current situation is that base-the-kitchen-sink includes both Prelude (as one of its exposed modules) and base-of-GHC-primitives (as a subset of all its modules). This however need not be the case. GHC installation can come with more than one primitive library, and base-the-kitchen-sink doesn’t need to expose everything from every primitive library.

The better way to think about this from user’s point of view is:

  1. There is a small set of standard types and values that are in scope by default, with no need to import them. This is Prelude.
  2. There is a wider set of relatively well-known types and values that are always available with any GHC installation, but one has to import a module before using them. The import of these modules does not depend on a package manger, and there’s no question of which version of a module one gets.
  3. Finally there’s a universe of Haskell modules that come from packages, and you gotta use a package manager before you can import one.

Note I didn’t mention base in the second item, because it’s irrelevant where these modules come from. The only thing that matters is that they are available by default, at least until you want to define your own package and need to specify the dependency bounds.

4 Likes

I’ve seen this sentiment pop up a few times in this thread. I feel like this is already the case with the “wired in” packages that GHC provides. Am I missing something?

For example if you install GHC 8.10.4 then you can rely on these packages being available, which includes many of the usual suspects: bytestring, containers, process, text, transformers, and so on.

And with respect to base and Prelude, my two cents are: Alternative preludes work great for applications but suck for libraries. As a library author I want my software to be approachable and usable. Unfortunately that means sticking with the lowest common denominator, which is base. Depending on wired in packages is fine, but on Hackage it’s still visually cluttered to depend on mtl, transformers, text, and bytestring even though they’re all provided “for free”.

2 Likes

What would happen to packages that import base like base == 4.*? I am grepping index-00 and I see lots of hits (some quite popular too — like hspec).

They’ll update.

3 Likes

No on is saying “Nah let’s keep foldl around, it’s fine.”

Unfortunately it’s not as simple as that (to my surprise).

1 Like

I don’t think you’re missing anything and I’m not sentimental about it. I’m just trying to explain, apparently not very well, that the discussion of the base package is not terribly relevant. To a beginner there is no distinction between base and text, because they are both available out of the box and one can import Data.List and Data.Text with exactly the same effort.

The trouble usually begins when you have to use a package manager to obtain and use a module outside the default set that comes with GHC. At least that’s my impression from Reddit; I don’t recall many questions about how to import a function from an already-available module. Merging text into base would not help with this problem at all.

3 Likes

To a beginner there is no distinction between base and text, because they are both available out of the box and one can import Data.List and Data.Text with exactly the same effort

One can import a later version of text than shipped with one’s GHC (and possibly even an earlier one!). Can one import a later version of base?

The first three words you quoted matter. If you’re talking about experts, then yes, expanding base would bring more problems to them and it wouldn’t help beginners either. Is this what you’re driving at?

Yes, if the beginner never needs a later version of text than was shipped with her GHC then you are correct that there is not much distinction between text and base.

I am, well, surprised that people would want to create yet another base library, to put it politely. We have so many of them, and another one is certainly one too many. None has been widely successful although many of them are, purely as a library, better than base.

An alternative base will not be successful.

Too few people would care about it, the ecosystem will simply not move there. (The obligatory XKCD has already been posted: https://xkcd.com/927/) What really needs to be adressed is the actual problem:

The development model of base is not good.

Important changes are not made, claiming backwards compatibility as a reason. But similar to what Michael Snoyman and other have pointed out, the Haskell ecosystem is ready to accept a certain amount of breaking changes, since GHC itself causes some breakage. If you want to freeze code, you freeze the GHC version, and thus the base version. Thus:

base does not need to be backwards compatible forever.

No library ever can reasonably be expected to be backwards compatible forever, and still be expected to be pleasant to work with after a few decades. And we really need to offer a pleasant base!

base is the standard library and it needs to be good!

The success of Haskell is to some non-negligible portion tied to a good standard library. Beginners use it, advanced folk stay with it for a long time. It’s the default starting place. So it needs to be kept in a good state. Once you accept that, it’s not the question anymore whether we clean up messes like head, String and foldl, but how fast we do it.

My opinion: Three versions back are sufficient. Mark historic warts as deprecated in one version, and remove them in a later version. Packages will update, or come out of fashion, and in the latter case, rightly so since they probably won’t run with newer GHCs anyways. Summary:

Slowly, but determinedly, clean up the existing base instead of making yet another one.

Split it up in base-ghc and base-prelude-and-kitchen-sink, fine, but work with it and make it good! There are enough people who will contribute the relevant code, it’s purely a coordination issue.

24 Likes

I don’t necessarily have much stock in what mentality is used in designing the new base, but I do definitely support decoupling it from GHC. More than just the release schedule, though, I’d argue for no hard dependency on its blessed implementations either; base should be a core library for whatever compiler is being used. Yes, GHC is currently the only player in town, but I’d speculate that part of that is because everything depends on base, and base exposes a lot of the internals. In order for an alternate compiler to be viable, it essentially has to replicate all of GHC —which is a rather prohibitive task— or advertise that it provides modules that it really doesn’t —and risk breaking things that do depend on what is (silently!) unimplemented.

Instead, I’d like to see a division into at least base and base-ghc or whatever other name is chosen, so that a toy- or a research compiler could be built to provide the (probably minimal) base and at least be used for simple packages, while any performance-sensitive projects can additionally depend on base-ghc for lower-level control of the in-memory representation (or whatever it is that people use the GHC modules for).

I don’t know exactly how that would be implemented, but I’d probably be initially looking at Backpack. If that means enshrining package managers, then so be it. (More likely, base may still wind up being special-cased by compilers, even if they don’t otherwise implement Backpack, but at least it would have a general target scope.) I’d still prefer that to locking in a “GHC” ecosystem vs a “XHC” ecosystem.

3 Likes

There are some established practices across other communities. If something is about to get removed, first it’s marked as deprecated, use xxx instead. So that people see it, aware about it and can plan changes in advance. And in next release it’s relatively safe to remove it with minimal broken code.

4 Likes

Today we released the 1.0.0.0 version of relude and it addresses one particular point, mentioned in this thread several times:

That being said, my largest reservation about relude’s particular approach is that it introduces its own abstractions and hides a good amount of functionality of the libraries on which it is based.

It (relude) also exposes the Text type, but it seems like I still have to register the text dependency to use the value.

Now relude reexports the main API of text, bytestring, containers and unordered-containers. So, reexporting the functionality from other libraries entirely without the need to add them to .cabal is something that is already possible in Haskell (not as convenient as I wanted, but still something). And it is just a matter of some time, devoted to this feature implementation.

12 Likes

Three cheers! Let’s make it happen!

I’d agree it’s mostly a coordination issue, but we also need to navigate cultural hurdles (why hasn’t this cleanup already happened?). We also have some strong egos to work with.

1 Like