# A modern take on the Prelude

**URL:** <https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619>\
**Category:** Uncategorized\
**Created:** [June 22, 2023, 11:32am UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619 "2023-06-22T11:32:26Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Wismill](https://avatars.discourse-cdn.com/v4/letter/w/ccd318/32.png) [@Wismill](https://discourse.haskell.org/u/Wismill)\
**Post date:** [June 22, 2023, 11:32am UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/1 "2023-06-22T11:32:26Z")

</div>

# A modern take on the Prelude

In the light of the ongoing discussions about the future of `base`, I would like  
to focus on a specific part of it: the _Prelude_.

# TL;DR

I think most issues with Prelude could be fixed by:

- aknowledging that the content of Prelude should be _context-dependent_.
- improving the _ergonomics_ of the use of alternative Preludes.

I expose my rough idea on how to improve the situation with a new language extension.

# The polysemy of Prelude

From the section 5.6 “Standard Prelude” of the Haskell 2010 Report:

> [Standard] Prelude and [standard] library modules differ from other modules in that their semantics (but not their implementation) are a fixed part of the Haskell language definition.

> The Prelude module is imported automatically into all modules as if by the statement ‘import Prelude’, if and only if it is not imported with an explicit import declaration.

> It is possible to construct and use a different module to serve in place of the Prelude. Other than the fact that it is implicitly imported, the Prelude is an ordinary Haskell module.

So there are two meanings for Prelude:

- The **standard Prelude** is a module which exports a _standard selection_ of definitions of the Haskell language.

- The **“Prelude“ feature** is “one _distinguished module_, Prelude, which is _imported into all modules by default_” (see section 5.0 of the Haskell 2010 Report).

In the report the two meanings are merged and I think this is the root of much issues, presented in the two following sections.

# Controversial standard definitions

Some standard definitions are _controversial_: e.g. the numeric hierarchy, `String`, partial functions such as head and tail, lazy IO, etc. Changing them means creating a new standard that may introduce breaking changes in code using the standard Prelude.

The obvious solutions are to not use them or redefine them in another library. Not using them can be enforced e.g. with HLint/warnings, while there are multiple alternative Preludes with new definitions or export list.

# Context-dependent commonly used definitions

The set of _commonly used_ definitions is dependent of the context:

- Some definitions of the Prelude may not be used (e.g. `IO` in a pure code).
- Some definitions of standard libraries are required but not exported by the standard Prelude: e.g. sized integral types such as `Int8` and `Word64`; classes such as `Bits` or `Ix`; `Char` predicates; functions such as `mapMaybe` or `sortBy`.
- Some definitions are not part of the standard-by-the-report libraries: `Text`, `Vector`, `Map`, `These`, etc.

“Commonly used” has no general definition, rather local ones. For example:

- In the context of the Haskell report and `base`, it is the result of what core Haskell developers agreed with the community.
- In the context of a domain-specific library, it may include the low-level definitions in `GHC.Exts` required for the implementation.
- In the context of an application using a library it may include its domain-specific definitions.

## Prelude _feature_ is neglected

There is no _obvious_ solution for the issues presented above. Changes to the _standard_ Prelude in `base` have raised heated debates. Rightly so: `Prelude` is usually imported in every Haskell file, so breakage is likely, thus conducting an impact assessment is not a trivial job. I would like to thank the CLC and people conducting such assessments!

Surprisingly, AFAIK improving the Prelude _feature_ has drawn few attention, while in my opinion it is a solution for both ergonomics and stability:

- Better ergonomics: choose a Prelude adapted for each context.
- Improved Stability: avoid need for migration using a light Prelude or fix breakages in only one place.

# Examples of current solutions

## _Without_ custom Prelude

It must rely on either:

- Explicit and detailed import of Prelude in every module. Does not seem a viable solution.
- Use `-XNoImplicitPrelude` and import explicitly from the original modules.

## Using _mixins_ in the `.cabal` file

Compatible with GHC ≥ 8.2 and cabal-version ≥ 2.0.

Add the following in your `.cabal` file:

```cabal
mixins: base hiding (Prelude)
      , my-prelude-library (My.Prelude as Prelude)

```

## Using `-XNoImplicitPrelude`

Add the following in your `.cabal` file:

```cabal
default-extensions: NoImplicitPrelude

```

Then import a custom Prelude module such as `My.Prelude` explicitly in every module.

# Towards a new solution?

Among existing proposals that I am aware, the following one look quite interesting:

- [Library-defined language extensions](https://github.com/ghc-proposals/ghc-proposals/pull/92). Unfortunately it is dormant and its scope is much wider than the Prelude.

I dream of the following solution, which would facilitate the choice of the Prelude:

- New GHC extension to set the Prelude module: e.g.

- New field in `.cabal` files: `Prelude: My.Prelude`, that would default to: `Prelude: Prelude`.

- New CLI option to use a specific Prelude in GHCi/cabal:

- New config file option to set the default value in GHCi/cabal/stack.

Then GHC could ship alternative yet standard-ish Preludes, such as:

- Prelude: the current Prelude, maintained to ensure compatibility.
- Prelude.Safe: e.g. without `head` or with an alternative implementation, etc.
- Prelude.Haskell2010: e.g. the exact Prelude of the Haskell 2010 report.
- Prelude.XXX: an hypothetical new Prelude that fixes the current flaws without backward compatibility. Notably it would be safer and exports `Text`, `Int8`, etc. Cannot be part of `base`, obviously.

I would like to know your opinion. Please correct me if there is any mistake, or prior art that I am not aware of! If there is sufficient interest I could open a GHC proposal.

---

<div class="post-metadata">

**Author:** ![hsyl20](https://avatars.discourse-cdn.com/v4/letter/h/b4bc9f/32.png) [@hsyl20](https://discourse.haskell.org/u/hsyl20)\
**Post date:** [June 22, 2023, 1:41pm UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/2 "2023-06-22T13:41:26Z")

</div>

Looks good!

I think we could easily have `-prelude-is` just like we have `-main-is`. Then we could use `{-# OPTIONS_GHC -prelude-is=My.Module#-}` for now until some support is added to Cabal.

---

<div class="post-metadata">

**Author:** ![hsyl20](https://avatars.discourse-cdn.com/v4/letter/h/b4bc9f/32.png) [@hsyl20](https://discourse.haskell.org/u/hsyl20)\
**Post date:** [June 22, 2023, 2:36pm UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/3 "2023-06-22T14:36:51Z")

</div>

Ok done:

- [#23551: Make Prelude module configurable · Issues · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/issues/23551)
- [!10704: Draft: Make Prelude module configurable with -prelude-is flag (#23551) · Merge requests · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/merge_requests/10704)

---

<div class="post-metadata">

**Author:** ![Bodigrim](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/bodigrim/32/1457_2.png) [@Bodigrim](https://discourse.haskell.org/u/Bodigrim)\
**Post date:** [June 22, 2023, 7:57pm UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/4 "2023-06-22T19:57:54Z")

</div>

@hsyl20 I’m not sure this warrants a new flag: [`base-noprelude`](https://hackage.haskell.org/package/base-noprelude) + [Cabal mixins](https://cabal.readthedocs.io/en/stable/cabal-package.html#pkg-field-mixins) to rename any module to `Prelude` should do the trick.

---

<div class="post-metadata">

**Author:** ![Wismill](https://avatars.discourse-cdn.com/v4/letter/w/ccd318/32.png) [@Wismill](https://discourse.haskell.org/u/Wismill)\
**Post date:** [June 23, 2023, 7:11am UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/5 "2023-06-23T07:11:25Z")

</div>

@hsyl20 thank you, that was fast! I like the name “prelude-is”. If your MR is accepted, I think it would be good to propose a specific syntax via a language extension, e.g. `{-# LANGUAGE PreludeIs My.Module #-}`. It could be done in a further release, if a period of experimentation is required. As for me I would like it as soon as possible. How can I help you?

---

<div class="post-metadata">

**Author:** ![bradrn](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/bradrn/32/264_2.png) [@bradrn](https://discourse.haskell.org/u/bradrn)\
**Post date:** [June 23, 2023, 7:35am UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/6 "2023-06-23T07:35:13Z")

</div>

I dislike that syntax, since no other language pragma takes an argument. Perhaps a better alternative would be a new field in the package description, since this feature is most useful at package scale.

---

<div class="post-metadata">

**Author:** ![jaror](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jaror/32/3271_2.png) [@jaror](https://discourse.haskell.org/u/jaror)\
**Post date:** [June 23, 2023, 7:39am UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/7 "2023-06-23T07:39:38Z")

</div>

Can you argue why the current solutions are not good enough? As you mention and @bodigrim repeats, we already have mixins which can do this. Are mixins really so bad that we need to introduce a whole new GHC option and Cabal field for one of their use cases?

---

<div class="post-metadata">

**Author:** ![ocharles](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ocharles/32/102_2.png) [@ocharles](https://discourse.haskell.org/u/ocharles)\
**Post date:** [June 23, 2023, 7:50am UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/8 "2023-06-23T07:50:42Z")

</div>

> [@jaror](#):
>
> we already have mixins which can do this

The last time I tried this it really confused Haskell Language Server. I otherwise liked the approach (though it was a bit tedious setting up for each of our components, it’s mostly a one-off cost). I should revisit that and file some issues!

---

<div class="post-metadata">

**Author:** ![hsyl20](https://avatars.discourse-cdn.com/v4/letter/h/b4bc9f/32.png) [@hsyl20](https://discourse.haskell.org/u/hsyl20)\
**Post date:** [June 23, 2023, 9:27am UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/9 "2023-06-23T09:27:20Z")

</div>

@Bodigrim `base-prelude` seems to be abandoned. Cabal mixins could be used but I like the option of having this directly in GHC. It seems more principled to me. At some point we could have `NoImplicitPrelude` the default and Cabal always explicitly specifying `-prelude-is`. This would uncouple `ghc` and `base` more 🙂

@Wismill specific syntax is always more difficult to get consensus on. `{-# LANGUAGE PreludeIs My.Module #-}` would be the first language pragma to take an argument as @bradrn wrote. Note that with the flag we will have `{-# OPTIONS_GHC -prelude-is My.Module #-}` which is quite similar, so special syntax won’t buy us much. Similarly in Cabal, we can use `ghc-options: -prelude-is My.Module` without special syntax for now.

> How can I help you?

I don’t think adding the flag requires a GHC proposal, but opinions may differ. If it’s deemed necessary, you can help by writing the proposal 🙂

---

<div class="post-metadata">

**Author:** ![Wismill](https://avatars.discourse-cdn.com/v4/letter/w/ccd318/32.png) [@Wismill](https://discourse.haskell.org/u/Wismill)\
**Post date:** [June 23, 2023, 12:18pm UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/10 "2023-06-23T12:18:57Z")

</div>

> specific syntax is always more difficult to get consensus on. `{-# LANGUAGE PreludeIs My.Module #-}` would be the first language pragma to take an argument as @bradrn wrote.

OK, I did not know it would be an issue. `{-# OPTIONS_GHC -prelude-is My.Module #-}` is fine, but it is not very elegant, is it?

> Can you argue why the current solutions are not good enough? As you mention and @bodigrim repeats, we already have mixins which can do this. Are mixins really so bad that we need to introduce a whole new GHC option and Cabal field for one of their use cases?

@jaror @bodigrim Mixins are a good feature, but I would like to make the Prelude a _first class feature_. I think nothing beats `prelude-is` in term of _readability:_ if I open a Cabal file, I could see easily that a custom prelude is used \[1\], even if I did not know about this field. Whereas with mixins you need to known how they work, then understands that the _boilerplate_ only mean using a custom Prelude. Also, how would you specify it when calling `ghci` or `cabal repl` _outside_ a project? What about the support of mixins in stack?

If we agree using custom Prelude is a good practice, then the main obstacle to a wider adoption is its _ergonomics_, because the technical solutions already exist.

@hsyl20 has already done a great job in starting a [GHC MR](https://gitlab.haskell.org/ghc/ghc/-/merge_requests/10704). Implementing a Cabal field would be nice; I would volunteer to do it if this thread shows interest to it. In fact the GHC flag and Cabal field are independent features, as the Cabal field can be implemented solely with (implicit) mixins. Using a GHC flag is a better option though, because it gives more flexibility (e.g. using multiple custom preludes in a lib) and make the Prelude feature really first class in GHC.

* * *

1. Recommend put it in the first fields.

---

<div class="post-metadata">

**Author:** ![blamario](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/blamario/32/860_2.png) [@blamario](https://discourse.haskell.org/u/blamario)\
**Post date:** [June 25, 2023, 2:02pm UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/12 "2023-06-25T14:02:43Z")

</div>

There is a simpler way to obtain a per-project Prelude today, with no per-module cost whatsoever. Note that the Haskell report quoted above says nothing about _where_ the `Prelude` module is obtained? I just ran an experiment:

```haskell
$ cat Prelude.hs 
{-# Language NoImplicitPrelude, PackageImports #-}
module Prelude where
import "base" Prelude (IO, putStrLn)
hello :: IO ()
hello = putStrLn "Hello World!"
$ cat PreludeTest.hs 
main = hello
$ ghc PreludeTest.hs
[1 of 3] Compiling Prelude ( Prelude.hs, Prelude.o )
[2 of 3] Compiling Main ( PreludeTest.hs, PreludeTest.o )
[3 of 3] Linking PreludeTest
[mario@fedora tmp]$ ./PreludeTest 
Hello World!

```

[EDIT] I just tested this setup with Cabal, and it works as well. All you need to do is add `other-modules: Prelude` stanza. There is _something_ funky about GHC though. When I tried `runhaskell` instead of `ghc` I got an error:

```haskell
$ runhaskell PreludeTest.hs 

<interactive>:1:1: error:
    attempting to use module ‘main:Prelude’ (./Prelude.hs) which is not loaded

```

I guess I’ll report this bug.

[EDIT] The bug is [known](https://gitlab.haskell.org/ghc/ghc/-/issues/10920).

---

<div class="post-metadata">

**Author:** ![Wismill](https://avatars.discourse-cdn.com/v4/letter/w/ccd318/32.png) [@Wismill](https://discourse.haskell.org/u/Wismill)\
**Post date:** [June 25, 2023, 2:18pm UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/13 "2023-06-25T14:18:07Z")

</div>

@blamario I know the trick, but you find its pitfall. See this [`containers` issue](https://github.com/haskell/containers/issues/898). That is why I did not propose this solution.

---

<div class="post-metadata">

**Author:** ![blamario](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/blamario/32/860_2.png) [@blamario](https://discourse.haskell.org/u/blamario)\
**Post date:** [June 25, 2023, 2:21pm UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/14 "2023-06-25T14:21:36Z")

</div>

The pitfall is clearly a GHCi bug. The correct response is to report and fix the bug, not to work around it.

---

<div class="post-metadata">

**Author:** ![Wismill](https://avatars.discourse-cdn.com/v4/letter/w/ccd318/32.png) [@Wismill](https://discourse.haskell.org/u/Wismill)\
**Post date:** [June 25, 2023, 3:51pm UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/15 "2023-06-25T15:51:32Z")

</div>

@blamario I think the issue is _already_ [reported](https://gitlab.haskell.org/ghc/ghc/-/issues/10920). I do not see the point to propose a solution that does not work properly with a current release of GHC. It seems better to use a workaround while the issue is fixed and backported. The mixins is a much better option.

---

<div class="post-metadata">

**Author:** ![hsyl20](https://avatars.discourse-cdn.com/v4/letter/h/b4bc9f/32.png) [@hsyl20](https://discourse.haskell.org/u/hsyl20)\
**Post date:** [July 4, 2023, 1:29pm UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/16 "2023-07-04T13:29:38Z")

</div>

@Wismill We discussed this quickly with the GHC team today. The conclusion was that it would useful to write a GHC proposal to get more feedback on the feature and on unintended consequences (in HLS, etc.).

---

<div class="post-metadata">

**Author:** ![Wismill](https://avatars.discourse-cdn.com/v4/letter/w/ccd318/32.png) [@Wismill](https://discourse.haskell.org/u/Wismill)\
**Post date:** [July 6, 2023, 10:38am UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/18 "2023-07-06T10:38:31Z")

</div>

We already discussed this [above](http://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/13).

---

<div class="post-metadata">

**Author:** ![slack1256](https://avatars.discourse-cdn.com/v4/letter/s/bbce88/32.png) [@slack1256](https://discourse.haskell.org/u/slack1256)\
**Post date:** [July 6, 2023, 7:17pm UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/20 "2023-07-06T19:17:09Z")

</div>

This thread made me realize we need at least a single popular signature and implementation that forces everybody to know how use mixins and backpack.

---

<div class="post-metadata">

**Author:** ![ulidtko](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ulidtko/32/4063_2.png) [@ulidtko](https://discourse.haskell.org/u/ulidtko)\
**Post date:** [April 17, 2024, 6:06pm UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/21 "2024-04-17T18:06:48Z")

</div>

Hi thread, I’m surfacing from a 2-day deep-dive into the subject of custom Prelude.

I agree with the rationale from @Wismill on two points:

- Leave `base` Prelude be, it’s simple to bash but very hard to fix. Further, everyone will like different preludes (in different applications / contexts) — and an imaginary perfect prelude won’t change that. Haskell should just enable easy switching to alternative preludes. Even better: trivial _project-local_ preludes.
- The ergonomics of prelude-the-feature is… quite awkward indeed, to put it mildly! I tried hard, but couldn’t get it to work, really — see a writeup in [haskell - How to correctly define your 'company' Prelude - Stack Overflow](https://stackoverflow.com/questions/35357198/how-to-correctly-define-your-company-prelude/78342760#78342760).

I trailed through an assortment of issues on GitHub; and it’s quite difficult to get all of `stack build`, `stack repl` & HLS working okay with a project-local `Prelude.hs` module. I couldn’t do it. As a stop-loss, I’ll instead take the pragmatic approach of saying `import MyLocalPrelude` everywhere.

The root issue appears to be [#10920: ghci can't load local Prelude module · Issues · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/issues/10920) already posted twice in the thread. In stack projects, HLS depends on `stack repl`, which in turn depends on `ghci` — and the entire “stack” of tooling topples with `attempting to use module ‘Prelude’ which is not loaded`. It is a blocker, there’s no viable workaround, I didn’t find any way to configure ghc-options or custom ghci-script for `stack repl` to Just Work.

… Now, to the `--prelude-is` proposal. Not a huge fan, and I’m not convinced it solves a real issue. The [GHC bug](https://gitlab.haskell.org/ghc/ghc/-/issues/10920) is a real issue.

---

<div class="post-metadata">

**Author:** ![theGhostJW](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/theghostjw/32/1302_2.png) [@theGhostJW](https://discourse.haskell.org/u/theGhostJW)\
**Post date:** [June 23, 2024, 7:30am UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/22 "2024-06-23T07:30:21Z")

</div>

looks like a fix is in the works: [!12530: Fix GHCi crashing on local Prelude · Merge requests · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/merge_requests/12530)

---

<div class="post-metadata">

**Author:** ![ulidtko](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ulidtko/32/4063_2.png) [@ulidtko](https://discourse.haskell.org/u/ulidtko)\
**Post date:** [April 6, 2025, 10:07am UTC](https://discourse.haskell.org/t/a-modern-take-on-the-prelude/6619/23 "2025-04-06T10:07:40Z")

</div>

Fix landed to 9.12.2 ✅
