Skip to content

Instantly share code, notes, and snippets.

@hasufell
Last active March 1, 2023 06:48
Show Gist options
  • Save hasufell/d9d33cc44f65163f493e09da311ef6a7 to your computer and use it in GitHub Desktop.
Save hasufell/d9d33cc44f65163f493e09da311ef6a7 to your computer and use it in GitHub Desktop.
Haskell CLC manifesto (Julian Ospald)

Manifesto

Here is my baseline on the API and maintenance of 'base', from which contributors can derive my potential support wrt proposals in advance (to some extent).

Purpose of base

The purpose of base is for providing a fairly stable interface of a standard library that follows:

  • good practice
  • idiomatic primitives
  • a consistent experience

And doing that while maintaining high quality and being mindful about performance characteristics.

Minimalist vs batteries included

I believe adding functionality to base that follows existing API and increases consistency/usability should be fairly non-controversial.

However I don't have a strong opinion on whether base should be minimal or batteries included. But I'd rather have more Data.List functions than less in it, because I find it annoying to search for them in additional packages and then not have confidence that they're maintained with high quality standards, causing space leaks or other issues.

Breakaging changes

'base' should try to avoid breakage as much as possible. Stability is a primary goal. Deprecation periods must be long (e.g. 3 years). For a breaking change to be accepted, it must prove to be of very high value.

New generations of API living alongside each other (compare with AFPP) are preferable over breaking changes. As such, stability has higher priority over ergonomics/usability.

Every breaking change needs extensive impact analysis on clc-stackage.

I classify breaking changes in 4 main categories that decide how likely I am to vote yes or no:

1. (security) bugfixes

Bugfixes that e.g. require stronger types or otherwise change the API are generally to be accepted. It has to be evaluated whether this can be done with a deprecation period and e.g. new functions/types.

This is the only case where I might accept a proposal with a breaking change that does not include a deprecation period. But it depends, e.g. how much of a "bugfix" this really is (see next section).

Sometimes, bugs have become expected behavior. Such cases have to be evaluated carefully.

An example of such a bugfix is the aeson breaking change (although not in base).

2. correctness improvements

This isn't about ergonomics or philosophical discussions like "is head a good idea?". It's about things that aren't mathematically/categorytheoretically correct or are fragile in the low-level sense: encoding issues, wrong representations (like FilePath), etc.

These may not be outright "bugs", because there's no obvious consensus about what's the right course, like with the broken ByteString IsString instance. But they may very well lead to bugs.

Such breaking changes generally need a deprecation period. I'll mostly vote no if a deprecation period is not possible to implement or suggest to provide new API instead.

This category is especially interesting for removal of bad/fishy API.

3. ergonomics improvements

This is the weakest category.

Ergonomics can range from "let's replace head with safeHead" to the Monad of no return. These don't improve correctness per se and are not bugfixes, but make things more pleasant/stricter/haskell-ish for a large audience and not just core-library or base maintainers.

There must be a compelling reason why this can't be implemented outside of base and there should be a non-intrusive migration path and a long deprecation period.

If a deprecation period is not possible to implement, I will vote no. I may vote no even if a deprecation period is possible. As an example: I'll vote no on anything that breaks API in the name of reducing partial functions.

4. performance improvements

Performance improvements that require breaking changes will generally get no vote from me. I'll suggest to implement them outside of base or as an additional API if possible.

Performance

While performance and laziness is an explicit concern of CLC, I do not think that base is a high-performance library.

We should strive to have as performant primitives as possible, but I do not require extensive evidence of performance characteristics when adding new idiomatic API.

I do require performance regression tests, when changing existent primitives, however, while acknowledging that this can be hard, since it may be deeply tied to GHC and the always changing optimizer/inlining behavior.

I may defer to other committee members with more expertise in this area and follow their recommendation.

Totality

Haskell is not a total language and will never be. I do not like API hand-holding and am an opponent of all the head/tail/HasCallStack shenanigans. Often times you can easily have local proof that your head is safe.

It would be nice to have better visibility of what functions are "safe" (not always easily decidable) and which are not, but it does not warrant disrupting API or annoying users about it.

I'm much more concerned about the brittleness of IO functions than I am about head and tail. Use the 'safe' package or an alternative prelude if this is important to you.

Documentation changes are always welcome and don't need CLC approval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment