# relude and the Haskell alternative prelude that avoids partial functions

> relude is an alternative standard library for Haskell that replaces the default Prelude with total functions, Text instead of String and a small dependency footprint, while refusing to rename anything or invent new abstractions. It is published on Hackage and Stackage, and the releases track GHC versions rather than a rapid feature cadence.

**kowainik/relude** — 🌀 Safe, performant, user-friendly and lightweight Haskell standard library

- Repository: https://github.com/kowainik/relude
- Website: https://kowainik.github.io/projects/relude
- Stars: 744 · Forks: 80
- Language: Haskell
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/kowainik-relude

## An alternative prelude that renames nothing

relude describes itself as a safe, performant, user-friendly and lightweight Haskell standard library, and the premise is that the default Prelude does not always satisfy a project's needs, which is the usual reason to reach for an alternative prelude at all.

What makes relude unusual is the set of things it refuses to do. The anti-goals are stated first in the README, and they are worth more than the feature list. One is rewriting base from the ground up: with relude you do not have to unlearn what you already knew. The second is inventing custom abstractions, on the grounds that learning abstractions is hard and new overwhelming concepts are best avoided. The third is renaming common definitions: if something is called foo in base it is also called foo in relude, and new definitions appear only when their benefit outweighs the learning curve.

That third anti-goal is the decision that shapes daily use. An alternative prelude is a place where naming is easy to change and hard to undo, and a library that renames nothing can be adopted without a vocabulary rewrite across a codebase.

The library is published on Hackage and appears in both Stackage LTS and Stackage Nightly, and the project documentation is at kowainik.github.io/projects/relude. A blog post by Type Classes introduces alternative preludes and highlights relude, and a talk covers the development principles behind it and compares the library with base.

## Total functions, and the types that catch the bugs

The second design principle is total programming. The argument is that using partial functions can lead to unexpected bugs and runtime exceptions even in pure code, and that the types of partial functions lie about their behaviour. The library does not claim you can always avoid partial functions, but it aims to encourage better practice and reduce the chance of introducing a bug.

The README shows the substitution table that carries most of this. head goes from head :: [a] -> a to head :: NonEmpty a -> a, so an empty list is no longer representable at the type level. tail follows the same path, from tail :: [a] -> [a] to tail :: NonEmpty a -> [a]. read becomes readMaybe, with a Maybe result instead of an exception, and fromJust becomes fromMaybe, which takes a default and returns a value rather than crashing.

Each of those changes moves a failure from runtime to compile time or to an explicit value in the type. That is the whole argument for an alternative prelude in one table, and it is a better argument than a longer list of extra functions.

The principle is described as making invalid states unrepresentable, and it is applied through types rather than through runtime checks wherever that is possible.

## whenNotNull: the difference one type makes

The README illustrates the type-safety principle with a single function, and the example is small enough to read in one go. The version relude prefers takes a list and a continuation over a NonEmpty list:

```haskell
   whenNotNull :: Applicative f => [a] -> (NonEmpty a -> f ()) -> f ()
```

The alternative signature, which the README presents as the version whose type could represent an unreachable state, passes the plain list to the continuation:

```haskell
   whenNotNull :: Applicative f => [a] -> ([a] -> f ()) -> f ()
```

The difference is not cosmetic. With the first signature, the continuation receives a NonEmpty list, so it is impossible for the code inside it to be handed an empty list, and the compiler enforces that at every call site. With the second signature, the type permits an empty list to reach code that may assume it has at least one element, so the assumption becomes an unchecked runtime fact.

This is the mechanism the library uses to justify its third principle. Rather than adding runtime guards, relude changes signatures so that the states that used to be unrepresentable stop being expressible. The function name, the argument types and the action are aligned with each other, which is the phrasing the README uses.

The general lesson for an evaluating team is that relude's value is not in the number of functions it re-exports, it is in the shape of the signatures it changes.

## Text instead of String and a sum that does not leak space

The fourth principle is performance, and it names specific choices rather than aspirations.

The first is Text over String, the list-of-characters representation that Haskell's standard library uses by default. The second is space-leak-free functions, with a custom performant sum and product named as examples. This is the kind of detail that matters in Haskell more than in most languages, because laziness makes accidental retention of a thunk easy to write and hard to see, and a sum implemented as a fold can hold on to the whole list while it runs.

The third is mechanical and predictable: the library introduces INLINE and SPECIALIZE pragmas where appropriate, so the compiler specialises and inlines the functions where it pays off. The fourth is making efficient container types more accessible, with Map, HashMap and Set named explicitly.

The performance principle therefore has two halves. One is choosing better data types at the API boundary, so Text is what you get rather than String. The other is making sure the implementation underneath does not waste time or space, which is where the pragmas and the hand-written sum and product come in.

The repository carries a benchmark/ directory, which is what you would expect from a library that makes performance claims at the level of individual functions, and the .ghci file at the root suggests the project is set up for interactive exploration during development.

## Re-exports, minimalism and the convenience list

The fifth principle is minimalism, expressed as a low number of dependencies, and it is in tension with the sixth, convenience. The README is explicit about the tension: the library does not force users to stick to a specific lens, text formatting or logging library, and where possible it depends only on boot libraries.

The convenience principle then lists what you get without adding dependencies to your own cabal file. You do not need to add containers, unordered-containers, text or bytestring to use the main API of those libraries. You do not need to import types such as NonEmpty, Text, Set, Reader, MVar or STM. Functions such as liftIO, fromMaybe and sortWith are available by default, and IO actions are lifted to MonadIO.

The practical effect is that a module with a single relude import gets a workable set of common types and functions in scope, which removes a layer of import noise from every file and removes a class of build configuration mistakes. The cost is that the library is now in your dependency list, and your program no longer reads the same as a program that imports base directly.

A dependency graph image is published in the repository, and the CHANGELOG.md tracks the history, so the size of what you are taking on can be inspected before you decide.

## Extra modules, HLint rules and documentation you can test

The ninth principle is exploration. The library reserves space for new ideas without breaking changes by using Extra.* modules that are not exported by default, which lets the maintainers add functionality without a major version bump and lets users opt in.

The documentation principle is the one that reduces friction most for a new user. The README lists a tutorial, a migration guide from Prelude, Haddock for every function with examples tested by doctest, documentation on the internal module structure, and relude-specific HLint rules in the .hlint.yaml file at the root of the repository. The .headroom.yaml and .stylish-haskell.yaml files point at further tooling, and a dedicated hlint/ directory in the repository suggests the lint rules are a first-class part of the project rather than an afterthought.

Doctest-tested examples are the detail that sets this apart. If every example in the Haddock is compiled and run as a test, the documentation cannot drift away from the implementation without the test suite failing, which is a stronger guarantee than prose documentation offers.

The tutorial itself is structured in parts that map onto the migration path, with sections on when to use an alternative prelude, getting started, the mixins approach, NoImplicitPrelude, base-noprelude, which is explicitly marked as not recommended, the difference from Prelude, the re-exports, what is new and a migration guide.

The user-friendliness principle is the summary of all of this: anyone should be able to migrate quickly, and basic familiarity with text and containers should be enough, though not necessary.

## Releases tied to GHC versions, and a quiet release line

The version history is short and each release is labelled with the GHC it targets: v1.0.0.1 was a minor docs fix on 2021-03-15, v1.1.0.0 targeted GHC 9.2 and was published on 2022-06-09, and v1.2.0.0 targeted GHC 9.4 and was published on 2023-03-01. The repository was last pushed to on 2026-09-27 and is not archived.

So the maintenance picture is a library whose last published release is v1.2.0.0 from 2023-03-01, with commits continuing in the default branch afterwards. For a library whose whole purpose is to be depended on by every module of an application, that is the fact to plan around: pinning to a release means pinning to a build of a particular GHC, and taking the newer commits means building from source or from a nightly snapshot.

That is not a criticism so much as a description of what an alternative prelude is. There is little pressure to release often, because the API is stable by design, the anti-goals forbid churn, and a new GHC version is the obvious trigger for a new tag.

The licence is MIT, with the licence file at the root of the repository. MIT is a permissive licence that permits commercial use and modification with the obligation to carry the copyright notice, and it is the licence that most Haskell projects expect. Reading the file rather than a summary is the sensible step, and none of this is legal advice.

The repository itself is compact: relude.cabal, src/, test/, benchmark/, the hlint/ directory of rules, a stack.yaml, the config files already mentioned, and the dependency graph image.

## Conclusion

Adopt relude if your Haskell project keeps tripping over partial functions, wants Text instead of String and prefers a prelude that re-exports the common libraries rather than making every module import them, and you are ready to read the migration guide before the first release. Skip it if your codebase leans on the exact Prelude semantics of base, since the differences are real, or if you want a library that introduces its own abstractions, which is explicitly listed as an anti-goal. Verify first that your GHC version matches a published release, since the tagged versions are tied to GHC 9.4 and GHC 9.2, and check the CHANGELOG.md, because the last release was v1.2.0.0 on 2023-03-01 while the last push was on 2026-09-27.

## FAQ

### What is relude?

relude is an alternative standard library for Haskell that replaces the default Prelude. It is built around total functions, Text instead of String, a small dependency footprint and re-exports of the common libraries, and it is published on Hackage and in Stackage LTS and Stackage Nightly.

### Does relude rename functions from base?

No. Renaming common definitions is listed as an anti-goal: if something is called foo in base it is also called foo in relude. New definitions are only added when their benefit outweighs the learning curve.

### How does relude deal with partial functions such as head and fromJust?

It offers total alternatives whose types make the failure impossible or explicit, for example head :: NonEmpty a -> a, tail :: NonEmpty a -> [a], readMaybe instead of read, and fromMaybe instead of fromJust.

### Which GHC versions do the relude releases target?

The tagged releases name their compiler: v1.1.0.0 targets GHC 9.2 and v1.2.0.0 targets GHC 9.4, with v1.2.0.0 published on 2023-03-01 and the last push to the repository on 2026-09-27.

### How do I migrate an existing project to relude?

The README points to a migration guide from Prelude, a tutorial, and a set of approaches for getting started including mixins, NoImplicitPrelude and base-noprelude, which is marked as not recommended. The examples in the Haddock are tested with doctest.

## Sources

- [kowainik/relude on GitHub](https://github.com/kowainik/relude)
- [License: MIT](https://github.com/kowainik/relude/blob/main/LICENSE)
- [Project website](https://kowainik.github.io/projects/relude)
- [README](https://github.com/kowainik/relude/blob/main/README.md)
- [Releases](https://github.com/kowainik/relude/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kowainik-relude
