A modern take on the Prelude

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.

2 Likes

Ok done:

4 Likes

@hsyl20 I’m not sure this warrants a new flag: base-noprelude + Cabal mixins to rename any module to Prelude should do the trick.

2 Likes

@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?

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.

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?

2 Likes

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!

2 Likes

@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 :slight_smile:

@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 :slight_smile:

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?

[^1]: Recommend put it in the first fields.

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. 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 Like

I don’t believe in a better Prelude that existing one for this reason. However I do use (local) custom prelude per project (.i.e I write my own prelude which could reexport part of a alternative prelude), so there is indeed a rationale for having a better to way to change the implicit prelude.

At the moment it is two lines in a file , the pragma with NoImplicitPrelude and the import the desired prelude. If the point of a new pragma is only to write the two line in one I am not sure there is much benefit.

However, If I can write it ONCE for the all project then I’m all for it. So having a prelude-is in the cabal file which set automatically NoImplicitPrelude and import the new prelude in every file is a good idea (as long as it allows local prelude.

4 Likes

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:

$ 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:

$ 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.

@blamario I know the trick, but you find its pitfall. See this containers issue. That is why I did not propose this solution.

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

1 Like

@blamario I think the issue is already reported. 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.

1 Like

@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.).

According to the doc you can have your custom prelude by just having a Prelude.hs file.

If this is true using a pre made custom prelude is just a matter of having

module Prelude (export X)
where
import YourFavoritePrelude as X

in your project. Unless I am missing something that solves everything doesn’t ? No need to new extension, modify the cabal file etc …

We already discussed this above.

Sorry, didn’t see it. Thanks

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.

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.

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 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 is a real issue.

6 Likes