quantities 0.3.0 → 0.4.0
raw patch · 10 files changed
+130/−89 lines, 10 filesdep ~Globdep ~doctestdep ~processnew-component:exe:quantities
Dependency ranges changed: Glob, doctest, process, regex-compat
Files
- CHANGES.md +10/−0
- LICENSE +1/−1
- library/Data/Quantities/Constructors.hs +5/−5
- library/Data/Quantities/Convert.hs +17/−13
- library/Data/Quantities/Data.hs +43/−44
- library/Data/Quantities/DefaultUnits.hs +3/−3
- library/Data/Quantities/Definitions.hs +3/−3
- library/Data/Quantities/ExprParser.hs +15/−13
- quantities.cabal +14/−7
- quantities.hs +19/−0
CHANGES.md view
@@ -1,6 +1,16 @@ quantities changelog ==================== +0.4 (unreleased)+----------------++- Changed type of `Quantity` to `Quantity a`, where `a` is the type of the+ magnitude. Note that `a` should be an instance of `Floating` to get the most+ out of the available operations on quantities.+- Added an executable that is an interface to `fromString`+- Can use " per " as a synonym for "=>" in `fromString`++ 0.3 (4-15-2014) ----------------
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2014, John David Reaver+Copyright (c) 2014-2015, John David Reaver All rights reserved. Redistribution and use in source and binary forms, with or without
library/Data/Quantities/Constructors.hs view
@@ -7,7 +7,7 @@ import Data.Quantities.ExprParser (parseExprQuant) -- | Default set of definitions that come predefined.-defaultDefinitions :: Either QuantityError Definitions+defaultDefinitions :: Either (QuantityError Double) Definitions defaultDefinitions = readDefinitions defaultDefString -- $setup@@ -25,7 +25,7 @@ -- -- 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.+-- 'ScalingFactorError') if the quantity to be converted to has a magnitude. -- -- >>> fromString "min => s" -- Right 60.0 second@@ -40,7 +40,7 @@ -- 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 :: String -> Either (QuantityError Double) (Quantity Double) fromString s = case defaultDefinitions of (Right d) -> parseExprQuant d s (Left d) -> Left d@@ -53,7 +53,7 @@ -- >>> let myFromString = fromString' d -- >>> myFromString "25 my_unit" -- Right 25.0 my_unit-fromString' :: Definitions -> String -> Either QuantityError Quantity+fromString' :: Definitions -> String -> Either (QuantityError Double) (Quantity Double) fromString' = parseExprQuant @@ -61,5 +61,5 @@ -- -- >>> unitsFromString "N * s" -- Right newton second-unitsFromString :: String -> Either QuantityError CompoundUnit+unitsFromString :: String -> Either (QuantityError Double) CompoundUnit unitsFromString = fmap units . fromString
library/Data/Quantities/Convert.hs view
@@ -15,7 +15,8 @@ -- -- >>> convert <$> fromString "m" <*> unitsFromString "ft" -- Right (Right 3.280839895013123 foot)-convert :: Quantity -> CompoundUnit -> Either QuantityError Quantity+convert :: (Fractional a) => Quantity a -> CompoundUnit ->+ Either (QuantityError a) (Quantity a) convert q us | hq /= hus = Left $ DifferentDefinitionsError (units q) us | otherwise = convert' (defs us) q us@@ -27,15 +28,16 @@ -- -- >>> convertBase <$> fromString "newton" -- Right 1000.0 gram meter / second ** 2-convertBase :: Quantity -> Quantity+convertBase :: (Fractional a) => Quantity a -> Quantity a convertBase x = convertBase' (defs' x) x -- | Convert quantity to given units.-convert' :: Definitions -> Quantity -> CompoundUnit -> Either QuantityError Quantity+convert' :: (Fractional a) => Definitions -> Quantity a -> CompoundUnit ->+ Either (QuantityError a) (Quantity a) convert' d q us' | dimq /= dimus = Left $ DimensionalityError (CompoundUnit d dimq) (CompoundUnit d dimus)- | otherwise = Right $ Quantity (mb/mb') us'+ | otherwise = Right $ Quantity (mb / realToFrac mb') us' where mb = magnitude $ convertBase' d q mb' = magnitude $ toBase d (sUnits us') dimq = dimensionality' d (units' q)@@ -44,19 +46,19 @@ -- | Convert a quantity to its base units.-convertBase' :: Definitions -> Quantity -> Quantity-convertBase' d (Quantity m us) = Quantity (m*mb) ub+convertBase' :: (Fractional a) => Definitions -> Quantity a -> Quantity a+convertBase' d (Quantity m us) = Quantity (m * realToFrac mb) ub where (Quantity mb ub) = toBase d (sUnits us) -- | Converts a composite unit to its base quantity-toBase :: Definitions -> [SimpleUnit] -> Quantity+toBase :: Definitions -> [SimpleUnit] -> Quantity Double toBase d = foldr (multiplyQuants . simpleToBase d) unityQuant where unityQuant = Quantity 1 (CompoundUnit d []) -- | Converts a simple unit to its base quantity.-simpleToBase :: Definitions -> SimpleUnit -> Quantity+simpleToBase :: Definitions -> SimpleUnit -> Quantity Double simpleToBase d (SimpleUnit sym pre pow) = Quantity m (CompoundUnit d us) where (m', u') = bases d M.! sym us = map (\(SimpleUnit s p pow') -> SimpleUnit s p (pow*pow')) u'@@ -67,7 +69,7 @@ -- -- >>> dimensionality <$> fromString "newton" -- Right [length] [mass] / [time] ** 2-dimensionality :: Quantity -> CompoundUnit+dimensionality :: Quantity a -> CompoundUnit dimensionality q = CompoundUnit (defs' q) dimUnits where dimUnits = dimensionality' (defs' q) (units' q) @@ -82,18 +84,20 @@ -- | Adds two quantities. Second quantity is converted to units of -- first quantity.-addQuants :: Quantity -> Quantity -> Either QuantityError Quantity+addQuants :: (Fractional a) => Quantity a -> Quantity a ->+ Either (QuantityError a) (Quantity a) addQuants = linearQuants (+) -- | Subtract two quantities. Second quantity is converted to units of -- first quantity.-subtractQuants :: Quantity -> Quantity -> Either QuantityError Quantity+subtractQuants :: (Fractional a) => Quantity a -> Quantity a ->+ Either (QuantityError a) (Quantity a) subtractQuants = linearQuants (-) -- | Helper function used in addQuants and subtractQuants.-linearQuants :: (Double -> Double -> Double) -> Quantity -> Quantity- -> Either QuantityError Quantity+linearQuants :: (Fractional a) => (a -> a -> a) -> Quantity a -> Quantity a+ -> Either (QuantityError a) (Quantity a) linearQuants f (Quantity m1 u1) q2 = case q of (Right q') -> Right $ Quantity (f m1 (magnitude q')) u1 (Left err) -> Left err
library/Data/Quantities/Data.hs view
@@ -56,34 +56,33 @@ -- | Combination of magnitude and units.-data Quantity = Quantity { magnitude :: Double- -- ^ Numerical magnitude of quantity.- --- -- >>> magnitude <$> fromString "100 N * m"- -- Right 100.0- , units :: CompoundUnit- -- ^ Units associated with quantity.- --- -- >>> units <$> fromString "3.4 m/s^2"- -- Right meter / second ** 2- } deriving (Ord)+data Quantity a = Quantity+ { magnitude :: a -- ^ Numerical magnitude of quantity.+ --+ -- >>> magnitude <$> fromString "100 N * m"+ -- Right 100.0+ , units :: CompoundUnit -- ^ Units associated with quantity.+ --+ -- >>> units <$> fromString "3.4 m/s^2"+ -- Right meter / second ** 2+ } deriving (Ord) -- | Convenience function to extract SimpleUnit collection from Quantity's -- CompoundUnit.-units' :: Quantity -> [SimpleUnit]+units' :: Quantity a -> [SimpleUnit] units' = sUnits . units -- | Convenience function to extract Definitions from Quantity's CompoundUnit.-defs' :: Quantity -> Definitions+defs' :: Quantity a -> Definitions defs' = defs . units -instance Show Quantity where+instance (Show a) => Show (Quantity a) where show (Quantity m us) = show m ++ " " ++ show us -- | Convenience function to make quantity with no definitions.-baseQuant :: Double -> [SimpleUnit] -> Quantity+baseQuant :: a -> [SimpleUnit] -> Quantity a baseQuant m us = Quantity m (CompoundUnit emptyDefinitions us) -- | Sort units but put negative units at end.@@ -91,31 +90,31 @@ showSort c = pos ++ neg where (pos, neg) = partition (\q -> power q > 0) c -instance Eq Quantity where+instance (Eq a) => Eq (Quantity a) where (Quantity m1 u1) == (Quantity m2 u2) = m1 == m2 && sort (sUnits u1) == sort (sUnits u2) -- | Custom error type-data QuantityError = UndefinedUnitError String- -- ^ Used when trying to parse an undefined unit.- | DimensionalityError CompoundUnit CompoundUnit- -- ^ Used when converting units that do not have the same- -- dimensionality (example: convert meter to second).- | UnitAlreadyDefinedError String- -- ^ Used internally when defining units and a unit is- -- already defined.- | PrefixAlreadyDefinedError String- -- ^ Used internally when defining units and a prefix is- -- already defined.- | ParserError String- -- ^ Used when a string cannot be parsed.- | 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)+data QuantityError a = UndefinedUnitError String+ -- ^ Used when trying to parse an undefined unit.+ | DimensionalityError CompoundUnit CompoundUnit+ -- ^ Used when converting units that do not have the same+ -- dimensionality (example: convert meter to second).+ | UnitAlreadyDefinedError String+ -- ^ Used internally when defining units and a unit is+ -- already defined.+ | PrefixAlreadyDefinedError String+ -- ^ Used internally when defining units and a prefix is+ -- already defined.+ | ParserError String+ -- ^ Used when a string cannot be parsed.+ | DifferentDefinitionsError CompoundUnit CompoundUnit+ -- ^ Used when two quantities come from different+ -- Definitions.+ | ScalingFactorError (Quantity a)+ -- ^ Used when a scaling factor is present in a unit+ -- conversion.+ deriving (Show, Eq) -- | Useful for monadic computations with 'QuantityError's. Some examples:@@ -134,10 +133,10 @@ -- > convertBase x -- -- Returns @Left (UndefinedUnitError "BADUNIT")@-type QuantityComputation = Either QuantityError+type QuantityComputation a = Either (QuantityError a) -- | Combines equivalent units and removes units with powers of zero.-reduceUnits :: Quantity -> Quantity+reduceUnits :: Quantity a -> Quantity a reduceUnits q = q { units = newUnits } where newUnits = (units q) { sUnits = reduceUnits' (units' q) } @@ -166,21 +165,21 @@ invertSimpleUnit (SimpleUnit s pr p) = SimpleUnit s pr (-p) -- | Multiplies two quantities.-multiplyQuants :: Quantity -> Quantity -> Quantity+multiplyQuants :: (Num a) => Quantity a -> Quantity a -> Quantity a multiplyQuants x y = reduceUnits $ Quantity mag newUnits where mag = magnitude x * magnitude y newUnits = (units x) { sUnits = units' x ++ units' y } -- | Divides two quantities.-divideQuants :: Quantity -> Quantity -> Quantity+divideQuants :: (Fractional a) => Quantity a -> Quantity a -> Quantity a divideQuants x y = reduceUnits $ Quantity mag newUnits where mag = magnitude x / magnitude y newUnits = (units x) { sUnits = units' x ++ invertUnits (units' y) } --- | Exponentiates a quantity with a double.-exptQuants :: Quantity -> Double -> Quantity+-- | Exponentiates a quantity with an integer+exptQuants :: (Real a, Floating a) => Quantity a -> a -> Quantity a exptQuants (Quantity x u) y = reduceUnits $ Quantity (x**y) newUnits- where expUnits = map (\(SimpleUnit s pr p) -> SimpleUnit s pr (p*y))+ where expUnits = map (\(SimpleUnit s pr p) -> SimpleUnit s pr (p * realToFrac y)) newUnits = u { sUnits = expUnits (sUnits u) } -- | Data type for the three definition types. Used to hold definitions@@ -192,7 +191,7 @@ , dimBase :: Symbol , defSynonyms ::[Symbol]} | UnitDefinition { defSymbol :: Symbol- , quantity :: Quantity+ , quantity :: Quantity Double , defSynonyms :: [Symbol]} deriving (Show, Eq, Ord) -- | Holds information about defined units, prefixes, and bases. Used when
library/Data/Quantities/DefaultUnits.hs view
@@ -13,9 +13,9 @@ -- second = min@. -- -- The reason these definitions aren't placed in a text file is so you don't--- have to operate your whole program in the IO monad. Theoretically, a user of--- this package can create their own definitions file or modify this one, but a--- mechanism for doing so hasn't been created yet.+-- have to operate your whole program in the IO monad. Users can copy this file+-- into their source and modify definitions, or simply add a few definitions to+-- the end of this string. -- -- These definitions are taken almost verbatim from the Pint unit conversion -- library for the Python programming language. Check them out on
library/Data/Quantities/Definitions.hs view
@@ -15,17 +15,17 @@ -- | Convert string of definitions into 'Definitions' structure. See source -- code for 'Data.Quantities.defaultDefString' for an example.-readDefinitions :: String -> Either QuantityError Definitions+readDefinitions :: String -> Either (QuantityError Double) Definitions readDefinitions s = addDefinitionsHash s <$> d where d = makeDefinitions (parseDefinitions s) -- | Monad used for addDefinition.-type DefineMonad = StateT Definitions (Either QuantityError)+type DefineMonad = StateT Definitions (Either (QuantityError Double)) -- | 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 :: [Definition] -> Either (QuantityError Double) Definitions makeDefinitions ds = execStateT (mapM addDefinition ds) emptyDefinitions -- | Add one definition to the definitions. Creates a new Definitions object
library/Data/Quantities/ExprParser.hs view
@@ -18,13 +18,14 @@ spaces' = many $ char ' ' -- | Parse quantity expression; addition and subtraction allowed.-parseExprQuant :: Definitions -> String -> Either QuantityError Quantity+parseExprQuant :: Definitions -> String ->+ Either (QuantityError Double) (Quantity Double) parseExprQuant d input = case parse (parseExpr d) "arithmetic" input of Left err -> Left $ ParserError $ show err Right val -> val -- | Simple type used for shorthand-type EQuant = Either QuantityError Quantity+type EQuant = Either (QuantityError Double) (Quantity Double) -- | Using already compiled definitions, parse expression. Also allows for -- expressions like "exp1 => exp2" in the middle, which converts the quantity@@ -70,7 +71,8 @@ parseSubtract = char '-' >> spaces' >> return subtractEQuants mulOp = try parseTimes <|> try parseDiv <|> parseImplicitTimes <?> "mulOp" where parseTimes = char '*' >> spaces' >> return multiplyEQuants- parseDiv = char '/' >> spaces' >> return divideEQuants+ parseDiv = divOp >> spaces' >> return divideEQuants+ where divOp = try (string "/") <|> string "per " parseImplicitTimes = return multiplyEQuants exptOp = try (opChoice >> spaces' >> return exptEQuants) <?> "expOp" where opChoice = string "^" <|> string "**"@@ -125,7 +127,7 @@ return $ Right $ q { units = (units q) { defs = d } } -- | Parses out prefixes and aliases from quantity's units.-preprocessQuantity :: Definitions -> Quantity -> Either QuantityError Quantity+preprocessQuantity :: Definitions -> Quantity Double -> EQuant preprocessQuantity d (Quantity x us) | null errs = Right $ Quantity x (CompoundUnit d us') | otherwise = Left $ head errs@@ -133,7 +135,7 @@ (errs, us') = partitionEithers ppUnits -- | Parses prefix and alias, if applicable, from a SimpleUnit.-preprocessUnit :: Definitions -> SimpleUnit -> Either QuantityError SimpleUnit+preprocessUnit :: Definitions -> SimpleUnit -> Either (QuantityError Double) SimpleUnit preprocessUnit d (SimpleUnit s _ p) | rs `elem` unitsList d = Right $ SimpleUnit ns np p | otherwise = Left $ UndefinedUnitError s@@ -159,10 +161,10 @@ -- | Converts string to a Quantity using an expression grammar parser. This -- parser does not parser addition or subtraction, and is used for unit -- definitions.-parseMultExpr :: Parser Quantity+parseMultExpr :: Parser (Quantity Double) parseMultExpr = spaces' >> parseMultExpr' <* spaces' -parseMultExpr', parseMultFactor, parseMultExpt, parseMultNestedExpr :: Parser Quantity+parseMultExpr', parseMultFactor, parseMultExpt, parseMultNestedExpr :: Parser (Quantity Double) parseMultExpr' = try parseMultFactorOp <|> parseMultFactor parseMultFactor = try parseMultExptOp <|> parseMultExpt parseMultExpt = try parseMultNestedExpr <|> parseSymbolNum@@ -171,11 +173,11 @@ <* spaces' <* char ')' <* spaces' <?> "parseNested" -parseMultExptOp, parseMultFactorOp :: Parser Quantity+parseMultExptOp, parseMultFactorOp :: Parser (Quantity Double) parseMultExptOp = parseMultExpt `chainl1` exptMultOp parseMultFactorOp = parseMultFactor `chainl1` mulMultOp -exptMultOp, mulMultOp :: Parser (Quantity -> Quantity -> Quantity)+exptMultOp, mulMultOp :: Parser (Quantity Double -> Quantity Double -> Quantity Double) mulMultOp = try parseTimes <|> try parseDiv <|> parseImplicitTimes <?> "mulMultOp" where parseTimes = char '*' >> spaces' >> return multiplyQuants parseDiv = char '/' >> spaces' >> return divideQuants@@ -183,18 +185,18 @@ exptMultOp = try (opChoice >> spaces' >> return exptMultQuants') <?> "expMultOp" where opChoice = string "^" <|> string "**" -exptMultQuants' :: Quantity -> Quantity -> Quantity+exptMultQuants' :: (Quantity Double -> Quantity Double -> Quantity Double) exptMultQuants' q (Quantity y (CompoundUnit _ [])) = exptQuants q y 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 :: Parser (Quantity Double) 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' :: Parser (Quantity Double) parseSymbol' = do neg <- option "" $ string "-" symf <- letter@@ -203,7 +205,7 @@ return $ baseQuant (timesSign neg 1) [SimpleUnit (symf : rest) "" 1] -- | Parent function for parseNum' to parse a number.-parseNum :: Parser Quantity+parseNum :: Parser (Quantity Double) parseNum = do num <- parseNum' return $ baseQuant num []
quantities.cabal view
@@ -1,10 +1,10 @@ name: quantities-version: 0.3.0-author: John David Reaver <jdreaver@adlerhorst.com>-maintainer: John David Reaver <jdreaver@adlerhorst.com>+version: 0.4.0+author: John David Reaver <johndreaver@gmail.com>+maintainer: John David Reaver <johndreaver@gmail.com> build-type: Simple cabal-version: >= 1.18-copyright: 2014 John David Reaver+copyright: Copyright (c) 2014-2015 John David Reaver license: BSD3 license-file: LICENSE synopsis: Unit conversion and manipulation library.@@ -18,7 +18,7 @@ or queried for its dimensionality. A user can also operate on quantities arithmetically, and doing so uses automatic unit conversion and- simplifcation.+ simplification. category: Data, Math, Physics homepage: http://github.com/jdreaver/quantities@@ -39,6 +39,13 @@ , Data.Quantities.DefinitionParser , Data.Quantities.ExprParser +executable quantities+ default-language: Haskell2010+ Hs-Source-Dirs: .+ Main-Is: quantities.hs+ build-depends: base >=4 && < 5, quantities, containers, mtl, parsec+ Buildable: True+ test-suite hspec default-language: Haskell2010 ghc-options: -Wall@@ -55,14 +62,14 @@ type: exitcode-stdio-1.0 test-suite doctest- build-depends: base, doctest == 0.9.*, Glob == 0.7.*+ build-depends: base, doctest, Glob 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.*+ build-depends: base, process, regex-compat default-language: Haskell2010 hs-source-dirs: test-suite main-is: DocCoverage.hs
+ quantities.hs view
@@ -0,0 +1,19 @@+import System.Environment+import System.Exit++import Data.Quantities++main :: IO ()+main = getArgs >>= parse++parse :: [String] -> IO ()+parse ["-h"] = usage >> exit+parse ["-v"] = version >> exit+parse [] = usage >> exit+parse [s] = putStr $ (++ "\n") $ either show show $ fromString s+parse _ = usage >> Main.die++usage = putStrLn "Usage: quantities [-vh] expression"+version = putStrLn "Haskell quantities 0.3.0"+exit = exitSuccess+die = exitWith (ExitFailure 1)