[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)
Set
#else
Set(..)
#endif
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
#endif
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
branch.
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
respectively.
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