Tracking Changes to Base

By Colin on 2020-03-05, updated 2020-05-05

The Hackage Matrix Builder is a great way to track how compatible a library is with historical versions of GHC (and therefore base).

As a library author, if you've ever attempted to support more than the most recent versions of GHC, then you've been tossed around the ocean of Applicative, Semigroup, and MonadFail, with CPP as your only olive branch.

This post is a reference for library authors to easily track how base evolves, and how to deal with its changes. This is so that we don't all have to repeat the same labour of digging through GHC release notes or the base CHANGELOG to find out what works. I will update this post as time goes on, so please feel free to bookmark it or even help update it.

Compatibility Chart

GHCbase versionStackage LTSCabalNotable Changes
8.104.14+RTS -xn for non-moving, concurrent GC
8.84.13163.0MonadFail exported by Prelude
fail removed from Monad
8.64.12142.4-funclutter-valid-hole-fits available
deriving via available
8.44.11122.2Semigroup is superclass of Monoid
Semigroup exported by Prelude
8.24.10112.0DerivingStrategies available
8.04.991.24MonadFail enters base
Semigroup enters base
7.104.861.22Applicative is superclass of Monad
Applicative exported by Prelude
7.84.721.18

Compatibility Pearls

Intent: For base earlier than 4.n, do X

  {-# LANGUAGE CPP #-}

  #if !MIN_VERSION_base (4,n,0)
  import FooBar
  #endif

A construction like #if __GLASGOW_HASKELL__ < 841 is also possible, but I find this less clear than comparing against base.

Backwards-compatible Monoid Instances

This will allow your library to support GHC >= 8.0. Excluding this CPP will limit your compatibility to GHC >= 8.4.

  {-# LANGUAGE CPP #-}

  #if !MIN_VERSION_base(4,11,0)
  import Data.Semigroup
  #endif

  instance Semigroup Foo where
    a <> b = ...

  instance Monoid Foo where
    mempty = ...

  #if !MIN_VERSION_base(4,11,0)
    mappend = (<>)
  #endif

Optional dependencies based on GHC version

For instance, until GHC 8.0, the Semigroup typeclass was not in base. The following will include the semigroups dependency, but only when it detects that you need it:

library
  build-depends:
    base >= 4.8 && < 5
    ...

  if !impl(ghc >= 8.0)
    build-depends:
      semigroups >= 0.8.4 && < 1

Multiple build-depends sections like this combine, they don't override one another.

*-compat packages

There are a number of packages that smooth the transition between compiler versions by backporting newer functionality. The major ones are:

To see all such libraries, search for "compat" on Hackage.

Misc. Ecosystem Pearls

Minimal dependencies for these

As of these-1 the project underwent a structural change. Its extended dependency graph is now "opt out", and the following can be added to a stack.yaml to accomplish this:

  flags:
    these:
      aeson: false
      assoc: false
      semigroupoids: false
      QuickCheck: false

Minimal dependencies for witherable

wither :: Applicative f => (a -> f (Maybe b)) -> t a -> f (t b) is a convenient function from the witherable package. As of 2019 December, the core typeclass and functions are available with minimal dependencies via the witherable-class library.

Your library's version in your code

This trick lets your library/program have programmatic access to the version: value you specified in your .cabal file.

  {-# LANGUAGE CPP #-}

  #ifndef CURRENT_PACKAGE_VERSION
  #define CURRENT_PACKAGE_VERSION "UNKNOWN"
  #endif

  ver :: Text
  ver = CURRENT_PACKAGE_VERSION

Resources