[Haskell-cafe] The amount of CPP we have to use is getting out of hand

Michael Orlitzky michael at orlitzky.com
Fri Jan 9 22:24:12 UTC 2015

On 01/09/2015 08:55 AM, Johan Tibell wrote:
> Hi,
> (This was initially written as a Google+ post, but I'm reposting it here to
> raise awareness of the issue.)
> The amount of CPP we have to use in Haskell is getting a bit out of
> hand. Here are the number of modules, per library, that use CPP for some of
> the libraries I maintain:
> containers 18/18
> hashable 4/5
> unordered-containers 6/9
> network 3/7
> cassava 4/16
> cabal/cabal-install 13/75
> cabal/Cabal 7/78
> ekg 1/15

I looked at a few of these, and some of the CPP could be avoided.
Whether or not the alternatives involve more work -- well, you be the judge.

1. TESTING constant. Here CPP is used to export internal stuff during
   testing, for example:

     module Data.Set (
     #if !defined(TESTING)

   This allows you to put the tests in a separate module, but give them
   access to internal functions. I'm torn on which solution is better,
   but I've settled on putting the tests in the module with the
   functions they test. You then have to depend on e.g. tasty, but who
   cares -- Cabal should be running the test suites anyway and bail out
   if they fail. That's (half of..) what they're for.

   You also have to import the Test.Crap in each module, but this
   bothers me less than I thought it would. If you use doctest to test
   your examples, then those tests have to go in the module with the
   functions themselves, so at that point there's no additional
   uncleanliness felt.

2. Optionally enable new features with newer GHCs. One example:

     #if MIN_VERSION_base(4,8,0)
     import Data.Coerce

   These are better addressed with git branches. Do your development on
   the master branch targeting the latest GHC, but also keep a branch
   for older GHC. The master branch would have "import Data.Coerce", but
   the "old_ghc" branch would not. It doesn't produce much extra work
   -- git is designed to do exactly this. Whenever you make a new
   commit on master, it's trivial to merge it back into the old_ghc

   Suppose your library foo is at version 1.5.0 when a new GHC is
   released. You can use the master branch for 1.6.0, using the new
   features. The next time you make a release, just release two new
   packages: 1.5.1 and 1.6.1 that target the old and new GHC

   This way you at least *work* off of a clean code base. Your new
   tricks in the master branch just look like a patch on top of
   what's in the old_ghc branch.

More information about the Haskell-Cafe mailing list