packages feed

quantities 0.2.0 → 0.3.0

raw patch · 12 files changed

+213/−53 lines, 12 filesdep +Globdep +doctestdep +processdep ~base

Dependencies added: Glob, doctest, process, regex-compat

Dependency ranges changed: base

Files

CHANGES.md view
@@ -1,16 +1,24 @@ quantities changelog ==================== --0.2 (unreleased)+0.3 (4-15-2014) ---------------- +- Added unit conversion to fromString using "x => y"+- Made dimensionality printing nicer.+- Added doctests and doc coverage tests.+++0.2 (4-6-2014)+--------------+ - Can handle arithmetical expressions in fromString - Added support for user-defined definitions+- Added check that definitions are the same for conversions   0.1 (3-31-2014)-----------------+---------------  - Full implementation of multiplicative dimensional quantities (no offsets yet   for temperatures).
library/Data/Quantities.hs view
@@ -50,6 +50,11 @@ import Data.Quantities.Definitions (readDefinitions) import Data.Quantities.DefaultUnits (defaultDefString) ++-- $setup+-- >>> import Control.Applicative++ -- $constructors -- -- Currently, one constructor is supported to create quantities: 'fromString'.@@ -98,10 +103,9 @@ -- 'defaultDefString'. Here is an example of adding a new unit called -- @metric_foot@. ----- > myDefString = defaultDefString ++ "\n" ++ "metric_foot = 300mm"--- > (Right d') = readDefinitions myDefString--- > myFromString = fromString' d'---+-- >>> let myDefString = defaultDefString ++ "\n" ++ "metric_foot = 300mm"+-- >>> let (Right d') = readDefinitions myDefString+-- >>> let myFromString = fromString' d' -- >>> myFromString "metric_foot" -- Right 1.0 metric_foot -- >>> convertBase <$> myFromString "metric_foot"@@ -117,8 +121,7 @@ -- done when a unit is created, and not when performing conversions. We try to -- catch when different definitions are used. ----- > (Right m)  = fromString "m"--- > (Right ft) = myFromString "ft"---+-- >>> let (Right m)  = fromString "m"+-- >>> let (Right ft) = myFromString "ft" -- >>> convert m (units ft) -- Left (DifferentDefinitionsError meter foot)
library/Data/Quantities/Constructors.hs view
@@ -1,3 +1,4 @@+-- | Defines the common constructors used to build quantities. module Data.Quantities.Constructors where  import Data.Quantities.Data@@ -5,11 +6,12 @@ import Data.Quantities.Definitions (readDefinitions) import Data.Quantities.ExprParser (parseExprQuant) +-- | Default set of definitions that come predefined. defaultDefinitions :: Either QuantityError Definitions defaultDefinitions = readDefinitions defaultDefString -d :: Definitions-(Right d) = defaultDefinitions+-- $setup+-- >>> import Data.Quantities  -- | Create a Quantity by parsing a string. Uses an 'UndefinedUnitError' for -- undefined units. Handles arithmetic expressions as well.@@ -21,22 +23,36 @@ -- >>> fromString "ft + 12in" -- Right 2.0 foot --+-- This function also supports unit conversions, by placing \"=>\" in between+-- two valid expressions. This behavior is undefined (and returns a+-- ScalingFactorError) if the quantity to be converted to has a magnitude.+--+-- >>> fromString "min => s"+-- Right 60.0 second+-- >>> fromString "2 ft + 6 in => ft"+-- Right 2.5 foot+-- >>> fromString "m => 3 ft"+-- Left (ScalingFactorError 3.0 foot)+-- -- Make sure not to use dimensional quantities in exponents. -- -- >>> fromString "m ** 2"--- Right 1.0 meter ** 2.0+-- Right 1.0 meter ** 2 -- >>> fromString "m ** (2s)" -- Left (ParserError "Used non-dimensionless exponent in ( Right 1.0 meter ) ** ( Right 2.0 second )") fromString :: String -> Either QuantityError Quantity-fromString = parseExprQuant d+fromString s = case defaultDefinitions of+                    (Right d) -> parseExprQuant d s+                    (Left d)  -> Left d + -- | Create quantities with custom definitions. ----- > (Right d) = readDefinitions myDefString--- > myFromString = fromString' d------ >>> myFromString "25 m/s"--- Right 25.0 meter / second+-- >>> let myDefString = defaultDefString ++ "\nmy_unit = 100 s"+-- >>> let (Right d) = readDefinitions myDefString+-- >>> let myFromString = fromString' d+-- >>> myFromString "25 my_unit"+-- Right 25.0 my_unit fromString' :: Definitions -> String -> Either QuantityError Quantity fromString' = parseExprQuant @@ -44,6 +60,6 @@ -- | Parse units from a string. Equivalent to @fmap units . fromString@ -- -- >>> unitsFromString "N * s"--- Right [newton,second]+-- Right newton second unitsFromString :: String -> Either QuantityError CompoundUnit unitsFromString = fmap units . fromString
library/Data/Quantities/Convert.hs view
@@ -1,3 +1,5 @@+-- | Module to perform unit conversions, compute dimensionality, and convert to+-- base units. module Data.Quantities.Convert where  import Data.List (sort)@@ -5,8 +7,9 @@  import Data.Quantities.Data -unityQuant :: Definitions -> Quantity-unityQuant d = Quantity 1 (CompoundUnit d [])+-- $setup+-- >>> import Control.Applicative+-- >>> import Data.Quantities  -- | Convert quantity to given units. --@@ -23,7 +26,7 @@ -- | Convert a quantity to its base units. -- -- >>> convertBase <$> fromString "newton"--- Right 1000.0 gram meter / second ** 2.0+-- Right 1000.0 gram meter / second ** 2 convertBase :: Quantity -> Quantity convertBase x = convertBase' (defs' x) x @@ -48,7 +51,8 @@  -- | Converts a composite unit to its base quantity toBase :: Definitions -> [SimpleUnit] -> Quantity-toBase d = foldr (multiplyQuants . simpleToBase d) (unityQuant d)+toBase d = foldr (multiplyQuants . simpleToBase d) unityQuant+  where unityQuant = Quantity 1 (CompoundUnit d [])   -- | Converts a simple unit to its base quantity.@@ -62,15 +66,18 @@ -- | Computes dimensionality of quantity. -- -- >>> dimensionality <$> fromString "newton"--- Right [length,mass,time ** -2.0]+-- Right [length] [mass] / [time] ** 2 dimensionality :: Quantity -> CompoundUnit dimensionality q = CompoundUnit (defs' q) dimUnits   where dimUnits = dimensionality' (defs' q) (units' q) +-- | Computes dimensionality of a list of SimpleUnits. Stores the+-- dimensionality as a list of SimpleUnits as well, so we don't need a whole+-- new type. dimensionality' :: Definitions -> [SimpleUnit] -> [SimpleUnit] dimensionality' d us = sort $ map dim ub   where ub = units' $ toBase d us-        dim (SimpleUnit sym _ pow) = SimpleUnit (unitTypes d M.! sym) "" pow+        dim (SimpleUnit sym _ pow) = SimpleUnit ('[' : unitTypes d M.! sym ++ "]") "" pow   -- | Adds two quantities. Second quantity is converted to units of@@ -84,7 +91,7 @@ subtractQuants :: Quantity -> Quantity -> Either QuantityError Quantity subtractQuants = linearQuants (-) -+-- | Helper function used in addQuants and subtractQuants. linearQuants :: (Double -> Double -> Double) -> Quantity -> Quantity                 -> Either QuantityError Quantity linearQuants f (Quantity m1 u1) q2 = case q of
library/Data/Quantities/Data.hs view
@@ -4,6 +4,10 @@ import Data.List (partition, sort) import qualified Data.Map as M +-- $setup+-- >>> import Control.Applicative+-- >>> import Data.Quantities+ -- | String representation of a unit. Examples: "meter", "foot" type Symbol = String @@ -18,28 +22,39 @@ instance Show SimpleUnit where   show (SimpleUnit s pr p)     | p == 1    = sym-    | otherwise = sym ++ " ** " ++ show p+    | otherwise = sym ++ " ** " ++ showPower p     where sym = pr ++ s -+-- | Data type to hold compound units, which are simple units multiplied+-- together. data CompoundUnit = CompoundUnit { defs   :: Definitions+                                   -- ^ Definitions used to create the units.                                  , sUnits :: [SimpleUnit]+                                   -- ^ List of SimpleUnits that is interpreted+                                   -- as the units being multiplied together.                                  } deriving (Eq, Ord)  instance Show CompoundUnit where   show (CompoundUnit _ us) = unwords . map showCompUnit' $ showSort us - -- | Show a single unit, but prepend with '/' if negative showCompUnit' :: SimpleUnit -> String-showCompUnit' (SimpleUnit s pr p)-  | p == 1    = sym-  | p == -1   = "/ " ++ sym-  | p < 0     = "/ " ++ sym ++ " ** " ++ show (-p)-  | otherwise = sym ++ " ** " ++ show p-  where sym = pr ++ s+showCompUnit' su@(SimpleUnit _ _ p)+  | p < 0     = "/ " ++ show (su { power = -p })+  | otherwise = show su +{-# ANN showPower "HLint: ignore Too strict if" #-}+-- | Removes decimal if almost integer.+showPower :: Double -> String+showPower d = if isInt d then show (round d :: Integer) else show d+  where isInt x = x == fromInteger (round x) +-- | Will be used when we allow pretty printing of fractional units.+showPrettyNum :: (Show a, Num a) => a -> String+showPrettyNum x = map (pretty M.!) $ show x+  where pretty = M.fromList $ zip "0123456789.-" "⁰¹²³⁴⁵⁶⁷⁸⁹·⁻"++ -- | Combination of magnitude and units. data Quantity = Quantity { magnitude :: Double                            -- ^ Numerical magnitude of quantity.@@ -50,7 +65,7 @@                            -- ^ Units associated with quantity.                            --                            -- >>> units <$> fromString "3.4 m/s^2"-                           -- Right [meter,second ** -2.0]+                           -- Right meter / second ** 2                          } deriving (Ord)  @@ -97,6 +112,9 @@                    | DifferentDefinitionsError CompoundUnit CompoundUnit                      -- ^ Used when two quantities come from different                      -- Definitions.+                   | ScalingFactorError Quantity+                     -- ^ Used when a scaling factor is present in a unit+                     -- conversion.                    deriving (Show, Eq)  @@ -116,7 +134,6 @@ -- >   convertBase x -- -- Returns @Left (UndefinedUnitError "BADUNIT")@- type QuantityComputation = Either QuantityError  -- | Combines equivalent units and removes units with powers of zero.@@ -124,19 +141,23 @@ reduceUnits q = q { units = newUnits }   where newUnits = (units q) { sUnits = reduceUnits' (units' q) } -reduceUnits', removeZeros :: [SimpleUnit] -> [SimpleUnit]-reduceUnits'  = removeZeros . reduceComp . sort+-- | Helper function for reduceUnits.+reduceUnits' :: [SimpleUnit] -> [SimpleUnit]+reduceUnits' = removeZeros . reduceComp . sort   where reduceComp [] = []         reduceComp (SimpleUnit x pr1 p1 : SimpleUnit y pr2 p2: xs)           | (x,pr1) == (y,pr2) = SimpleUnit x pr1 (p1+p2) : reduceComp xs           | otherwise = SimpleUnit x pr1 p1 : reduceComp (SimpleUnit y pr2 p2 : xs)         reduceComp (x:xs) = x : reduceComp xs -+-- | Removes units with powers of zero that are left over from other+-- computations.+removeZeros :: [SimpleUnit] -> [SimpleUnit] removeZeros [] = [] removeZeros (SimpleUnit _ _ 0.0 : xs) = removeZeros xs removeZeros (x:xs) = x : removeZeros xs +-- | Negate the powers of a list of SimpleUnits. invertUnits :: [SimpleUnit] -> [SimpleUnit] invertUnits = map invertSimpleUnit @@ -162,7 +183,8 @@   where expUnits = map (\(SimpleUnit s pr p) -> SimpleUnit s pr (p*y))         newUnits = u { sUnits = expUnits (sUnits u) } -+-- | Data type for the three definition types. Used to hold definitions+-- information when parsing. data Definition = PrefixDefinition { defPrefix   :: Symbol                                    , factor      :: Double                                    , defSynonyms :: [Symbol]}@@ -173,7 +195,8 @@                                    , quantity    :: Quantity                                    , defSynonyms :: [Symbol]} deriving (Show, Eq, Ord) -+-- | Holds information about defined units, prefixes, and bases. Used when+-- parsing new units and performing units conversions. data Definitions = Definitions { bases          :: M.Map String (Double, [SimpleUnit])                                  -- ^ Map from symbol to base units and                                  -- conversion factor to those units.@@ -203,6 +226,7 @@ instance Eq Definitions where   d1 == d2 = defStringHash d1 == defStringHash d2 +-- | Default, empty set of definitions. emptyDefinitions :: Definitions emptyDefinitions = Definitions { bases          = M.empty                                , synonyms       = M.empty
library/Data/Quantities/DefaultUnits.hs view
@@ -1,3 +1,4 @@+-- | Holds the default definition string. module Data.Quantities.DefaultUnits (defaultDefString) where  -- | View the source code for this declaration to see what units and prefixes
library/Data/Quantities/DefinitionParser.hs view
@@ -1,3 +1,4 @@+-- | Uses parsec to parse definition lines. module Data.Quantities.DefinitionParser where  import Control.Applicative ((<*))@@ -13,6 +14,7 @@   Left err -> error (show err) >> []   Right val -> val +-- | Parsec parser for definitions. parseDefinitions' :: Parser [Definition] parseDefinitions' = many parseDef <* eof @@ -31,6 +33,7 @@ -- comment :: Parser String -- comment = char '#' >> many (noneOf "\n") +-- | Custom eol parsec parser. eol :: Parser Char eol = char '\n' @@ -42,6 +45,7 @@   syns <- many (try parseSynonym)   return $ UnitDefinition s e syns +-- | Parses a unit definition. Example: foot = 12 in = ft = feet parseUnitDef :: Parser Definition parseUnitDef = do   sym   <- parseSymbol <* spaces <* char '='@@ -50,11 +54,12 @@   -- skipMany comment   return $ UnitDefinition sym quant [] +-- | Parses the synonyms at the end of a base or unit definition. parseSynonym :: Parser Symbol parseSynonym = spaces >> char '=' >> spaces >> parseSymbol <* spaces  --- | Parse line containing base definition+-- | Parse line containing base definition. -- Ex: meter = [length] = m parseBaseLine :: Parser Definition parseBaseLine = do@@ -62,6 +67,7 @@   syns <- many (try parseSynonym)   return $ BaseDefinition sym f syns +-- | Parse the base of a base definition. Example: [length] -> length parseBase :: Parser (Symbol, Symbol) parseBase = do   sym <- parseSymbol <* spaces <* char '='@@ -78,6 +84,7 @@   syns <- many (try parsePrefixSynonym)   return $ PrefixDefinition p f syns +-- | Parse the prefix part of a prefix definition. Example: yocto- -> yocto parsePrefix :: Parser (Symbol, Double) parsePrefix = do   pre <- many1 letter <* char '-' <* spaces <* char '='@@ -87,7 +94,7 @@     return (pre, magnitude facQuant)     else fail "No units allowed in prefix definitions" -+-- | Parse the synonyms for a prefix definition. parsePrefixSynonym :: Parser Symbol parsePrefixSynonym = spaces >> char '=' >> spaces >> parseSymbol <* char '-' <* spaces 
library/Data/Quantities/Definitions.hs view
@@ -1,3 +1,4 @@+-- | This module builds a Definitions object from a string. module Data.Quantities.Definitions where  import Control.Applicative ((<$>))@@ -18,10 +19,18 @@ readDefinitions s = addDefinitionsHash s <$> d   where d = makeDefinitions (parseDefinitions s) +-- | Monad used for addDefinition. type DefineMonad = StateT Definitions (Either QuantityError)++-- | Converts a list of definitions to the Definitions data structure. Modifies+-- an emptyDefinitions object by combining the incremental additions of each+-- Definition. makeDefinitions :: [Definition] -> Either QuantityError Definitions makeDefinitions ds = execStateT (mapM addDefinition ds) emptyDefinitions +-- | Add one definition to the definitions. Creates a new Definitions object+-- using the information in the Definition, and then unions the Definitions in+-- the state monad with this new object. addDefinition :: Definition -> DefineMonad ()  addDefinition (PrefixDefinition sym fac syns) = do
library/Data/Quantities/ExprParser.hs view
@@ -10,23 +10,44 @@ import Numeric (readFloat) import Text.ParserCombinators.Parsec -import Data.Quantities.Convert (addQuants, subtractQuants)+import Data.Quantities.Convert (addQuants, subtractQuants, convert) import Data.Quantities.Data +-- | Alternate definition for spaces. Just actual spaces. spaces' :: Parser String spaces' = many $ char ' ' +-- | Parse quantity expression; addition and subtraction allowed. parseExprQuant :: Definitions -> String -> Either QuantityError Quantity parseExprQuant d input = case parse (parseExpr d) "arithmetic" input of   Left err  -> Left $ ParserError $ show err   Right val -> val --- | Converts string to a Quantity using an expression grammar parser.+-- | Simple type used for shorthand type EQuant = Either QuantityError Quantity +-- | Using already compiled definitions, parse expression. Also allows for+-- expressions like "exp1 => exp2" in the middle, which converts the quantity+-- exp1 into the units of the quantity exp2. parseExpr :: Definitions -> Parser EQuant-parseExpr d = spaces' >> parseExpr' d <* spaces'+parseExpr d = (try (parseConvertExpr d) <|> parseSingle) <* eof+  where parseSingle = spaces >> parseExpr' d <* spaces +-- | Parser that accepts "=>" in between two expressions.+parseConvertExpr :: Definitions -> Parser EQuant+parseConvertExpr d = do+  _ <- spaces'+  exp1 <- parseExpr' d <* spaces+  exp2 <- string "=>" >> spaces >> parseExpr' d+  _ <- spaces'+  return $ do+    e1 <- exp1+    e2 <- exp2+    u2 <- units <$> exp2+    case magnitude e2 of+      1.0 -> convert e1 u2+      _   -> Left $ ScalingFactorError e2+ parseExpr', parseTerm :: Definitions -> Parser EQuant parseFactor, parseExpt, parseNestedExpr :: Definitions -> Parser EQuant parseExpr'      d = try (parseTermOp d)     <|> parseTerm d@@ -54,27 +75,32 @@ exptOp = try (opChoice >> spaces' >> return exptEQuants) <?> "expOp"   where opChoice = string "^" <|> string "**" -+-- | Modification of addQuants to account for Either QuantityError Quantity. addEQuants :: EQuant -> EQuant -> EQuant addEQuants (Right a) (Right b) = addQuants a b addEQuants (Left a) _          = Left a addEQuants _ (Left b)          = Left b +-- | Modification of subtractQuants to account for Either QuantityError Quantity. subtractEQuants :: EQuant -> EQuant -> EQuant subtractEQuants (Right a) (Right b) = subtractQuants a b subtractEQuants (Left a) _          = Left a subtractEQuants _ (Left b)          = Left b +-- | Modification of multiplyQuants to account for Either QuantityError Quantity. multiplyEQuants :: EQuant -> EQuant -> EQuant multiplyEQuants (Right a) (Right b) = Right $ multiplyQuants a b multiplyEQuants (Left a) _          = Left a multiplyEQuants _ (Left b)          = Left b +-- | Modification of divideQuants to account for Either QuantityError Quantity. divideEQuants :: EQuant -> EQuant -> EQuant divideEQuants (Right a) (Right b) = Right $ divideQuants a b divideEQuants (Left a) _          = Left a divideEQuants _ (Left b)          = Left b +-- | Modification of exptQuants to account for Either QuantityError Quantity.+-- Returns error if dimensional quantity used in exponent. exptEQuants :: EQuant -> EQuant -> EQuant exptEQuants (Left a) _          = Left a exptEQuants _ (Left b)          = Left b@@ -82,20 +108,23 @@ exptEQuants a b  = Left $ ParserError $ "Used non-dimensionless exponent in " ++ showq   where showq = unwords ["(", show a, ") ** (", show b, ")"] +-- | Modification of parseSymbolNum to handle parsing errors. parseESymbolNum :: Definitions -> Parser EQuant parseESymbolNum d = try (parseENum d) <|> parseESymbol d +-- | Parses a symbol and then parses a prefix form that symbol. parseESymbol :: Definitions -> Parser EQuant parseESymbol d = do   q <- parseSymbol'   return $ preprocessQuantity d q +-- | Parse a number and insert the given definitions into the CompoundUnit. parseENum :: Definitions -> Parser EQuant parseENum d = do   q <- parseNum   return $ Right $ q { units = (units q) { defs = d } } --- | Convert prefixes and synonyms+-- | Parses out prefixes and aliases from quantity's units. preprocessQuantity :: Definitions -> Quantity -> Either QuantityError Quantity preprocessQuantity d (Quantity x us)   | null errs = Right $ Quantity x (CompoundUnit d us')@@ -103,6 +132,7 @@     where ppUnits     = map (preprocessUnit d) (sUnits us)           (errs, us') = partitionEithers ppUnits +-- | Parses prefix and alias, if applicable, from a SimpleUnit. preprocessUnit :: Definitions -> SimpleUnit -> Either QuantityError SimpleUnit preprocessUnit d (SimpleUnit s _ p)   | rs `elem` unitsList d = Right $ SimpleUnit ns np p@@ -111,7 +141,7 @@         np       = prefixSynonyms d M.! rp         ns       = synonyms d M.! rs -+-- | Try to parse a prefix from a symbol. Otherwise, just return the symbol. prefixParser :: Definitions -> String -> (String, String) prefixParser d input = if input `elem` unitsList d                           then ("", input)@@ -119,7 +149,7 @@                             Left _ -> ("", input)                             Right val -> splitAt (length val) input -+-- | Helper function for prefixParser that is a Parsec parser. prefixParser' :: Definitions -> Parser String prefixParser' d = do   pr <- choice $ map (try . string) (prefixes d)@@ -158,9 +188,12 @@ exptMultQuants' a b  = error $ "Used non-dimensionless exponent in " ++ showq   where showq = unwords ["(", show a, ") ** (", show b, ")"] +-- | Parse either a symbol or a number. parseSymbolNum :: Parser Quantity parseSymbolNum = try parseNum <|> parseSymbol' +-- | Parse a symbol with an optional negative sign. A symbol can contain+-- alphanumeric characters and the character '_'. parseSymbol' :: Parser Quantity parseSymbol' = do   neg  <- option "" $ string "-"@@ -169,11 +202,14 @@   _ <- spaces'   return $ baseQuant (timesSign neg 1) [SimpleUnit (symf : rest) "" 1] +-- | Parent function for parseNum' to parse a number. parseNum :: Parser Quantity parseNum = do   num <- parseNum'   return $ baseQuant num [] +-- | Meat of number parser. Parse digits with an optional negative sign and+-- optional exponential. For example, -5.2e4. parseNum' :: Parser Double parseNum' = do   neg <- option "" $ string "-"@@ -183,6 +219,8 @@   _ <- spaces'   return $ timesSign neg $ fst $ head $ readFloat $ whole ++ decimal ++ exponential +-- | Parses just the exponential part of a number. For example, parses "4" from+-- "-5.2e4". parseExponential :: Parser String parseExponential = do   e <- string "e"@@ -190,6 +228,7 @@   pow <- many1 digit   return $ e ++ neg ++ pow +-- | Negate a number if the first argument is a negative sign. timesSign :: String -> Double -> Double timesSign sign x   | sign == "-" = -x
quantities.cabal view
@@ -1,5 +1,5 @@ name:                 quantities-version:              0.2.0+version:              0.3.0 author:               John David Reaver <jdreaver@adlerhorst.com> maintainer:           John David Reaver <jdreaver@adlerhorst.com> build-type:           Simple@@ -52,6 +52,20 @@     default-language: Haskell2010     hs-source-dirs:   test-suite     main-is:          HLint.hs+    type:             exitcode-stdio-1.0++test-suite doctest+    build-depends:    base, doctest == 0.9.*, Glob == 0.7.*+    default-language: Haskell2010+    hs-source-dirs:   test-suite+    main-is:          DocTest.hs+    type:             exitcode-stdio-1.0++test-suite coverage+    build-depends:    base, process == 1.1.*, regex-compat == 0.95.*+    default-language: Haskell2010+    hs-source-dirs:   test-suite+    main-is:          DocCoverage.hs     type:             exitcode-stdio-1.0  source-repository head
+ test-suite/DocCoverage.hs view
@@ -0,0 +1,25 @@+module Main (main) where++import Data.List (genericLength)+import Data.Maybe (catMaybes)+import System.Exit (exitFailure, exitSuccess)+import System.Process (readProcess)+import Text.Regex (matchRegex, mkRegex)++average :: (Fractional a, Real b) => [b] -> a+average xs = realToFrac (sum xs) / genericLength xs++expected :: Fractional a => a+expected = 90++main :: IO ()+main = do+    output <- readProcess "cabal" ["haddock"] ""+    if average (match output) >= expected+        then exitSuccess+        else putStr output >> exitFailure++match :: String -> [Int]+match = fmap read . concat . catMaybes . fmap (matchRegex pattern) . lines+  where+    pattern = mkRegex "^ *([0-9]*)% "
+ test-suite/DocTest.hs view
@@ -0,0 +1,7 @@+module Main (main) where++import System.FilePath.Glob (glob)+import Test.DocTest (doctest)++main :: IO ()+main = glob "library/**/*.hs" >>= doctest