Skip to main content

Haskell Breaking Changes

There has been a lot of discussion about breaking changes over the past month. It started in the Make Eq type class single method thread on the Libraries mailing list before spreading to Twitter, and Sandy Maguire’s excellent Dragging Haskell Kicking and Screaming into the Century of the Fruitbat blog post received many comments on /r/haskell. There are benefits to making breaking changes as well as benefits to avoiding them, and finding a good balance can be challenging when different members of the community feel strongly about opposing sides.

Personally, I think that it is worth fixing mistakes in the long run. For example, I am really glad that fail is no longer part of Monad. (Note that the (/=) change is trivial in comparison.) In particular, I do not mind breaking changes as long as they are made with consideration of the consequences and library developers do the work to make libraries compatible with a not-too-small range of dependency versions. (If a library does not support a generous range of dependency versions, it can be very problematic for those who use the library. See my article on Software Extent.)

Maintaining libraries to work with incompatible versions of a dependency can be challenging, and it also often involves conditional compilation using CPP, which can make code more difficult to follow by both humans and tools. I sympathize with the opinions of those in the backwards compatibility camp. Some library developers feel that there is a lot of burden on them to keep up with breaking changes. Developers may find it difficult to allocate sufficient time for software maintenance due to changes in their life (health, family, work, interests, etc.). The work can be particularly frustrating when they feel that the changes are not even necessary.

Michael Snoyman related a story about the lack of breakage in the Rust ecosystem. Lack of breakage leads to a lower cost of maintenance. Frequent breaking changes in Haskell raise the cost of maintenance. This is most problematic in companies with over-booked schedules and few Haskell developers, where the developers are not allocated time/resources to continually maintain software. Software can be difficult to maintain over years of change, especially when the original developers are no longer in the company and the software only builds using “ancient” versions of tools and dependencies. Maintenance issues with Haskell projects can unfortunately make Haskell look like a poor choice of technology to use for new projects in the company.

Sandy proposes making lots of breaking changes at once instead of spreading them out over many releases. From a library perspective, I think that this can indeed be helpful, as the developer can then consider how to provide compatibility over the many changes, with minimal CPP usage. From an application perspective, it is likely that some people prefer it while other find it overwhelming. It probably does not matter for the developer who is tasked with updating software that has not been touched in years, as they would face many breaking changes at once anyway.

The community is now discussing decoupling base and GHC, with a concrete plan. One thing that I really like about the stated goals is that they include the following:

  • Every major version of base should support 3 adjacent major versions of GHC
  • Every major version of GHC should support 3 adjacent major versions of base

Here is a summary of the first and last releases of each major GHC version in the past five years:

First Release Last Release base
8.0.1 (2016-05-21) 8.0.2 (2017-01-11) 4.9
8.2.1 (2017-07-22) 8.2.2 (2017-07-22) 4.10
8.4.1 (2018-03-08) 8.4.4 (2018-10-14) 4.11
8.6.1 (2018-09-21) 8.6.5 (2019-04-23) 4.12
8.8.1 (2019-08-25) 8.8.4 (2020-07-15) 4.13
8.10.1 (2020-03-24) 8.10.7 (2021-08-27) 4.14
9.0.1 (2021-02-04) 4.15
9.2.1 (2021-10-29) 4.16

If this policy were already in effect, then base-4.16 would be compatible with GHC 8.10, GHC 9.0, and GHC 9.2, while GHC 9.2 would be compatible with base-4.14, base-4.15 and base-4.16. Note that this results in a little bit less than two years of support. From an industry perspective, I feel that two years is very short. On the contrary, perhaps my goal of providing five years of support in my libraries is too ambitious?