Immutable Publishing Policy

Summary

This is a policy for publishing Haskell packages. It was published in February of 2022. The aim of this policy is to remove all breaking changes to downstream users of packages, where feasible. It is most applicable to packages which have users, and/or have a more or less stable API. It prioritizes downstream users at the expense of author convenience.

It has been implemented in the lucid package.

Terms

By “levels” in this description, we mean “module” is the first level, and “package” is the level above.

Rules

Haskell-specific note:

Steps

This differs substantially to the PVP. ⚠️

Rationale

Long-term steps

Infrastructure/technical support

Compatibility with the PVP

You can do both, the IPP is a subset of behavior within the PVP (but upper bounds aren’t required or needed by the IPP). In practice, people following just the PVP and not the IPP will be a pain for you as Haskellers change things and cause breakage all the time. But don’t bother people about it. Just do your best.

When to use this policy

I propose that you should:

For packages already published, you can apply IPP immediately.

FAQs

Do you just want Unison?

Sure, but that’s a whole new language. Haskell is our language of choice, so let’s do our best to make it nicer to use.

It’s not worth doing because we should have machines automate dependencies based on fine grained uses

That’s true. Here in the real world, however, no such automation exists.

Security fixes can be breaking changes, we should make bad code go away

You don’t know how and in what setting your users are using your libraries. It’s not your place to bully them into changing their programs because you know better.

Shouldn’t old code be garbage collected?

Yes, I covered deprecation above. I also covered removing old code and the ending of maintenance above.

What if I add a constructor to a type? I’d have to duplicate my whole API!

Yes. If you add a new constructor then you’ll indeed require more of the consumer of your package to form a complete pattern match. Duplicate the type. You changed a namespace.

I’ll repeat myself: the IPP is about valuing the user’s time over the maintainer’s time. It’s not all about you and your needs. Think about others who have to consume your work.

Why have package versions when you are going to duplicate an entire module or package when you make a breaking change?

I already covered this above, but let’s rewrite in a different way: Package versions in Haskell are broken. They force all package maintainers to move in lock step. You can only practically use one version of a package in your installed package scope.

If you want to keep using foo-1.2.3 and another package you’re using wants foo-2.0.0, you shouldn’t be forced to change all your code. You should be given the option to migrate between them.