Summary: I wrote an extra
library, which contains lots of my commonly used functions.
When starting to write Bake, my first step was to copy a lot of utility functions from Shake - things like fst3
(select the first element of a triple), concatMapM
(monadic version of concatMap
), withCurrentDirectory
(setCurrentDirectory
under bracket
). None of these functions are specific to either Bake or Shake, and indeed, many of the ones in Shake had originally came from HLint. Copy and pasting code is horrible, so I extracted the best functions into a common library which I named extra. Unlike the copy/paste versions in each package, I then wrote plenty of tests, made sure the functions worked in the presence of exceptions, did basic performance optimisation and filled in some obvious gaps in functionality.
I'm now using the extra
library in all the packages above, plus things like ghcid and Hoogle. Interestingly, I'm finding my one-off scripts are making particularly heavy use of the extra
functions. I wrote this package to reduce my maintenance burden, but welcome other users of extra
.
My goal for the extra
library is simple additions to the standard Haskell libraries, just filling out missing functionality, not inventing new concepts. In some cases, later versions of the standard libraries provide the functions I want, so there extra
makes them available all the way back to GHC 7.2, reducing the amount of CPP in my projects. A few examples:
Control.Monad.Extra.concatMapM
provides a monadic version ofconcatMap
, in the same way thatmapM
is a monadic version ofmap
.Data.Tuple.Extra.fst3
provides a function to get the first element of a triple.Control.Exception.Extra.retry
provides a function that retries anIO
action a number of times.System.Environment.Extra.lookupEnv
is a function available in GHC 7.6 and above. On GHC 7.6 and above this package reexports the version fromSystem.Environment
while on GHC 7.4 and below it defines an equivalent version.
The module Extra
documents all functions provided by the library, so is a good place to go to see what is on offer. Modules such as Data.List.Extra
provide extra functions over Data.List
and also reexport Data.List
. Users are recommended to replace Data.List
imports with Data.List.Extra
if they need the extra functionality.
Which functions?
When selecting functions I have been guided by a few principles.
- I have been using most of these functions in my packages - they have proved useful enough to be worth copying/pasting into each project.
- The functions follow the spirit of the original Prelude/base libraries. I am happy to provide partial functions (e.g.
fromRight
), and functions which are specialisations of more generic functions (whenJust
). - Most of the functions have trivial implementations. If a beginner couldn't write the function, it probably doesn't belong here.
- I have defined only a few new data types or type aliases. It's a package for defining new utilities on existing types, not new types or concepts.
Testing
One feature I particularly like about this library is that the documentation comments are tests. A few examples:
Just True ||^ undefined == Just True
retry 3 (fail "die") == fail "die"
whenJust (Just 1) print == print 1
\x -> fromRight (Right x) == x
\x -> fromRight (Left x) == undefined
These equalities are more semantic equality than Haskell's value equality. Things with lambda's are run through QuickCheck. Things which print to stdout
are captured, so the print 1
test really does a print, which is scraped and compared to the LHS. I run these tests by passing them through a preprocessor, which spits out this code, which I then run with some specialised testing functions.
2 comments:
have you tried using base-compat for backwards compatibility with older versions of GHC?
Greg, not really, but partly because of the way I got to this point. I was adding features I wanted to be in future versions of base. Sometimes I get lucky and then they are in future versions, and I then use extra to migrate to the official ones. It ends up doing quite a few things base-compat does, but more as an accident, it certainly doesn't intend to replace base-compat, more complement it.
Post a Comment