It can be difficult to write code that maintains compatibility with multiple versions of dependencies. In many cases, a library author can decide to require users to upgrade to a more recent version of dependencies. However, in the case of libraries bundled with GHC, this means upgrading the compiler, which in many cases is not an option.
One possible response would be to try and lock down the APIs for this libraries to avoid any migration headaches. However, such an approach would ultimately lead to quite convoluted APIs at the core of our ecosystem.
Instead, the core libraries committee has recommended an approach of releasing compatibility packages for each version of the base package. This document will lay out the design decisions we recommend, and why we decided on them.
Consider a very simple (and obviously fake) example. Suppose base for some terrible reason decided to provide a function which attempts to parse an
Int and, on failure, uses a default value of 0:
module Parse where parseInt :: String -> Int parseInt s = case reads s of (i,""):_ -> i _ -> 0
This module makes it into base version 4.6 but then, when releasing base 4.7, we decide that the default value should really be an argument to the function instead of hard-coded to 0. Thus, our type signature changes to:
parseInt :: Int -> String -> Int
Making this change in base itself is trivial. The problem is for all the user code out there using
parseInt. In order to allow user code to be compatible with both version 4.6 and 4.7 of base, the user must rely on techniques such as Cabal CPP macros, e.g.:
myFunc s = 5 + #if MIN_VERSION_base(4, 7, 0) parseInt 0 s #else parseInt s #endif
Such conditionals are tedious and error-prone. The goal of this proposal is to simplify the life of users trying to write code that will compile against multiple GHC versions, while allowing GHC-bundled libraries to be updated in reasonable manner.