Thoughts on `NoImplicitPrelude` as a default?

I’m considering making NoImplicitPrelude one of the default extensions in my .cabal files.

Yes, I know that means adding all Prelude imports explicitly to the top of the file (as I plan on using -Wmissing-import-lists and -Werror). But, I do that for every other module import, and editor support makes that relatively easy.

Many languages do not have an implicit “prelude”. Admittedly C will give you some basic inbuilt numeric operators for free and some types like int etc) which you don’t get without Prelude in GHC (although interestingly the unit type () you don’t need import) but my point is in C you don’t get a wide range of container and IO operations implicitly like one does with Prelude.

Admittedly various languages take different approaches on how much is “implicitly” imported, some closer to C and some closer to Haskell, but I also feel with Haskell is that the Prelude is a little outdated at this point. It’s very list focused instead of generically handling many structures, has incomplete functions like head and tail, and it’s approach to IO is arguably questionable with things like getContents.

My (IMHO) feeling is that the Prelude was designed for a time where Haskell was used for single file mathematical focused programs in an academic setting, and giving it a elevated status today, at least as a whole, is no longer as appropriate as it may have been in the past.

Of course, there’s many perfectly cromulent things in the Prelude, like Int, Bool, IO, Maybe and the associated functions, as well as type classes like Eq and Ord. Indeed I don’t feel the need to go full replacement Prelude, at least not as a general rule, but polluting the namespace of every file with Prelude seems a bit silly considering that arguably much of it isn’t particularly more special than many functions in other base libraries.

I feel like this decision is just a matter of taste, but I haven’t seen much other Haskell code use NoImplicitPrelude by default. So I just wanted to check this approach with the community, because sometimes when something tastes bad for EVERYONE else it probably means it’s probably poison in someway, and I’d like that to be highlighted if it is.

Note that I do use -Wmissing-import-lists anyway, so it’ll just be an extra line for Prelude imports.

NoImplicitPrelude is up there with OverloadedStrings for me - whenever I develop a library, I inevitably making some sort of library-specific prelude and import that in my modules. Inside it I can pick and re-export common dependencies, including Prelude if I am just adding functionality and there arent any name collisions.

To me there are two reasonable choices:

  • Not using NoImplicitPrelude
  • Using `NoImplicitPrelude` with your own custom prelude module.

Different projects have different needs and so having different things in scope by default makes a lot of sense. But I don’t see the point of NoImplicitPrelude if you don’t define some kind of standard scope to use in it’s stead.

The only name clash from prelude that annoys regularly is id so I don’t worry too much about polluting the namespace.

SML, Scheme, Racket, Java all have implicit preludes. My impression is that most post-1980 languages do; C and Fortran are the minority exceptions, and highly correlated with how they’re 1970s languages back when there was no namespacing, and “modules” were at best 3rd-class.

I benefit from implicit preludes 99.9% of the time; for the other 0.01% I don’t mind a manual import Prelude hiding (fmap, <*>, liftA2, pure, (>>=)).

You can describe me as one of those mathematical academic single files. Sure.

But I will point out again that the highly industrial language Java has implicit prelude too.

If you don’t want all of Prelude polluting your namespace, then just explicitly import what you want from the Prelude. Then nothing is implicit.

*blinks*

I don’t want Prelude polluting my namespace, and I do import explicitly what I want from Prelude, and I do it in my custom Prelude. Does this bother you for some reason?

I only said that I like using the NoImplicitPrelude extension, not that I want it turned on by default :slight_smile:

I have no trouble putting the single required NoImplicitPrelude in my cabal file, I find import MyLib.Prelude to be more ergonomic than import Prelude except (all, the, things) in every file, and I find the advantages of consolidating library-wide common imports to a custom Prelude to result in a significant reduction of the import verbage in general, not just for masking Prelude.

But again, this is a matter of personal taste. I did not ask for any changes

Thanks for all your replies. It’s been food for thought.

What I’m now considering is the following:

  1. Define my own prelude (or use someone else’s) with a limited set of exports, as the module Prelude.
  2. Don’t import base, but instead import base-noprelude.
  3. Not use NoImplicitPrelude.

That means I can define what I like as a limited set of implicit imports (things like Int, Bool, Eq, Num etc) but not things like head and tail and undefined which I really would prefer people be more explicit about, and perhaps include some things that are currently outside of Prelude, like Foldable.

These will be implicitly imported in all of my modules, as apparently it’s the Prelude in scope that is imported by default, not the one from base.

The only issue with this approach is that base-noprelude seemed to have been have not updated time. So I’ve got two follow up questions:

  1. Do people thing this is a good approach?
  2. If yes, has anyone else base-noprelude recently (perhaps with another name), or is this something I’d need to get back up to date?

You need undefined! Specifically, when you are working top-down.

With undefined you can quickly “sketch” functions. That is write the signature without implementing them:

ancillary :: Bar -> Int
ancillary = undefined -- I will complete this later.

Now you can use ancillary somewhere else:

magnitude :: [Bar] -> Int
magnitudes bs = sum $ map ancillary bs

The compiler will check the types are fine. Only once type-checking passes the program will run (and explode).

So once you are sure types work fin together (and only then), you implement ancillary.
(Nothing more annoying than implementing a function before, and then realising you do not have exactly the signature you need.)


For functions like undefined and similar (“useful, but do not leave me in production code”) you can do what cabal does:

traceShowM :: (Show a, Applicative f) => a -> f ()
traceShowM = Debug.Trace.traceShowM
{-# DEPRECATED traceShowM "Don't leave me in the code" #-}

You re-export them with a warning, so you will not forget about having them where they should not be.

I would recommend using todo from placeholder instead, so you can have warnings about stuff being left in without having to re-export everything.

I would recommend NoImplicitPrelude over using an outdated package. At work, we use NoImplicitPrelude and explicit import Relude. For open-source, we just use base’s Prelude so as not to force an extra dep onto library users.

I tend to browse packages’ code a lot and don’t like the implicit imports.
Hackage source viewer is not that great for navigating the Mystery Codebase where everything is magic and implicit.
It is extra not-great if the magic comes from some other package and defined in some other place rather than in the imports section of a module.