dimensional 1.0.0.0 → 1.0.1.0
raw patch · 9 files changed
+425/−275 lines, 9 filesdep ~basedep ~exact-pi
Dependency ranges changed: base, exact-pi
Files
- CHANGELOG.md +90/−0
- README.md +3/−6
- dimensional.cabal +6/−3
- src/Numeric/Units/Dimensional.hs +51/−257
- src/Numeric/Units/Dimensional/Coercion.hs +27/−0
- src/Numeric/Units/Dimensional/Dimensions.hs +1/−1
- src/Numeric/Units/Dimensional/Dynamic.hs +9/−6
- src/Numeric/Units/Dimensional/Internal.hs +237/−0
- src/Numeric/Units/Dimensional/NonSI.hs +1/−2
+ CHANGELOG.md view
@@ -0,0 +1,90 @@+1.0.1.0 (2015-11) +------------------ +* Added Numeric.Units.Dimensional.Coercion module. +* Bumped exact-pi dependency to < 0.5. +* Restored changelog. +* Addressed issues with documentation. + +1.0.0.0 (2015-11) +------------------ +* Changed to DataKinds and ClosedTypeFamilies encoding of dimensions. +* Added names and exact values to `Unit`s. +* Added `AnyUnit` and `AnyQuantity` for quantities whose dimension is statically unknown. +* Added Storable and Unbox instances for `Quantity`. +* Added dimensionally-polymorphic `siUnit` for the coherent SI base unit of any dimension. +* Added some additional units. + +0.13.0.2 (2015-04) +------------------ +* Corrected definition of lumen. + + +0.13.0.1 (2014-09) +------------------ +* Bumped time dependency to < 1.6. + + +0.13 (2014-02) +-------------- +* Bump major version (should have been done in previous version). + + +0.12.3 (2014-02) +---------------- +* Bump numtype dependency to 1.1 (GHC 7.8.1 compatibility fix). +* Added `Torque`. +* Added D.. for the type synonym quantities (e.g., `Angle`). + + +0.12.2 (2013-11) +---------------- +* Added `FirstMassMoment`, `MomentOfInertia`, `AngularMomentum`. +* Improved unit numerics. + + +0.12.1 (2013-07) +---------------- +* Typeable Dimensionals. + + +0.12 (2013-06) +-------------- +* Polymorphic `_0` (closes issue 39). +* Added `astronomicalUnit`. +* Added imperial volume units. +* Added 'mil' (=inch/1000). +* Added [`tau`][3]. +* Added `KinematicViscosity`. + +[3]: http://tauday.com/tau-manifesto + + +0.10.1.2 (2011-09) +------------------ +* Bumped time dependency to < 1.5. + + +0.10.1.2 (2011-08) +------------------ +* Bumped time dependency to < 1.4. + + +0.10.1 (2011-08) +------------------ +GHC 7.2.1 compatibility fix: + +* Increased CGS context-stack to 30. + + +0.10 (2011-05) +------------- +See the [announcement][2]. + +[2]: http://flygdynamikern.blogspot.se/2011/05/announce-dimensional-010.html + + +0.9 (2011-04) +------------- +See the [announcement][1]. + +[1]: http://flygdynamikern.blogspot.se/2011/04/announce-dimensional-09.html
README.md view
@@ -1,5 +1,4 @@-dimensional -============== +# dimensional This library provides statically-checked dimensional arithmetic for physical quantities, using the 7 SI base dimensions. @@ -9,8 +8,7 @@ [](https://travis-ci.org/bjornbm/dimensional-dk) [](http://hackage.haskell.org/package/dimensional) -Usage ------ +## Usage Simply importing `Numeric.Units.Dimensional.Prelude` provides access to dimensional arithmetic opertors, SI units and other common units accepted for use with the SI, and convenient aliases for quantities with commonly used dimensions. @@ -46,8 +44,7 @@ differenceFromStandardValue = approximateAccelerationDueToGravityOnEarth /~ gee ``` -Contributing ------------- +## Contributing For project information (issues, updates, wiki, examples) see: https://github.com/bjornbm/dimensional-dk
dimensional.cabal view
@@ -1,5 +1,5 @@ name: dimensional -version: 1.0.0.0 +version: 1.0.1.0 license: BSD3 license-file: LICENSE copyright: Bjorn Buckwalter 2006-2015 @@ -32,6 +32,7 @@ Requires GHC 7.8 or later. extra-source-files: README.md, + CHANGELOG.md, examples/README, examples/GM.lhs @@ -42,13 +43,14 @@ library build-depends: base >= 4.7 && < 5, deepseq >= 1.3, - exact-pi >= 0.2.1.1 && < 0.3, + exact-pi >= 0.2.1.1 && < 0.5, numtype-dk >= 0.5 && < 1.1, vector >= 0.10 hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall exposed-modules: Numeric.Units.Dimensional, + Numeric.Units.Dimensional.Coercion, Numeric.Units.Dimensional.Prelude, Numeric.Units.Dimensional.Quantities, Numeric.Units.Dimensional.SIUnits, @@ -61,7 +63,8 @@ Numeric.Units.Dimensional.UnitNames, Numeric.Units.Dimensional.UnitNames.InterchangeNames, Numeric.Units.Dimensional.Variants - other-modules: Numeric.Units.Dimensional.UnitNames.Internal + other-modules: Numeric.Units.Dimensional.Internal, + Numeric.Units.Dimensional.UnitNames.Internal test-suite tests type: exitcode-stdio-1.0
src/Numeric/Units/Dimensional.hs view
@@ -117,22 +117,28 @@ in its error messages. >>> x = 1 *~ meter + 1 *~ second -Couldn't match expected type `Pos1' against inferred type `Zero' - Expected type: Unit DLength t - Inferred type: Unit DTime a +Couldn't match type 'Numeric.NumType.DK.Integers.Zero + with 'Numeric.NumType.DK.Integers.Pos1 + Expected type: Unit 'Metric DLength a + Actual type: Unit 'Metric DTime a In the second argument of `(*~)', namely `second' In the second argument of `(+)', namely `1 *~ second' In other cases the error messages aren't very friendly. >>> x = 1 *~ meter / (1 *~ second) + 1 *~ kilo gram -Couldn't match expected type `Zero' - against inferred type `Neg Zero' - When using functional dependencies to combine - Sub Zero (Pos Zero) (Neg Zero), - arising from use of `/' at ... - Sub Zero (Pos Zero) Zero, - arising from use of `/' at ... +Couldn't match type 'Numeric.NumType.DK.Integers.Zero + with 'Numeric.NumType.DK.Integers.Neg1 + Expected type: Quantity DMass a + Actual type: Dimensional + ('Numeric.Units.Dimensional.Variants.DQuantity + Numeric.Units.Dimensional.Variants.* 'Numeric.Units.Dimensional.Variants.DQuantity) + (DLength / DTime) + a + In the first argument of `(+)', namely `1 *~ meter / (1 *~ second)' + In the expression: 1 *~ meter / (1 *~ second) + 1 *~ kilo gram + In an equation for `x': + x = 1 *~ meter / (1 *~ second) + 1 *~ kilo gram It is the author's experience that the usefullness of the compiler error messages is more often than not limited to pinpointing the @@ -149,18 +155,6 @@ There are also plenty of elementary functions to add. The 'Floating' class can be used as reference. -Another useful addition would be decent 'Show' and 'Read' instances. -The 'show' implementation could output the numerical value and the -unit expressed in (base?) SI units, along the lines of: - -> instance (Fractional a, Show a) => Show (Length a) -> where show x = show (x /~ meter) ++ " m" - -Additional functions could be provided for "showing" with any unit -and prefix. The 'read' implementation should be able to read values -with any unit and prefix. It is not clear to the author how to best -implement these. - Additional physics models could be implemented. See <#note3 [3]> for ideas. == Related work @@ -238,9 +232,9 @@ where import Prelude - ( Show, Eq(..), Ord, Bounded(..), Num, Fractional, Floating, Real, RealFloat, Functor, fmap - , (.), flip, show, (++), fromIntegral, fromInteger, fromRational, error, max, succ - , Int, Integer, Integral, ($), uncurry, realToFrac, otherwise, undefined, String + ( Eq(..), Num, Fractional, Floating, Real, RealFloat, Functor, fmap + , (.), flip, (++), fromIntegral, fromInteger, fromRational, error, max, succ + , Int, Integer, Integral, ($), uncurry, realToFrac, otherwise ) import qualified Prelude import Numeric.NumType.DK.Integers @@ -248,28 +242,17 @@ , pos2, pos3 , KnownTypeInt, toNum ) -import Control.Applicative -import Control.DeepSeq -import Control.Monad (liftM) -import Data.Coerce (coerce) import Data.Data import Data.ExactPi import Data.Foldable (Foldable(foldr, foldl')) import Data.Maybe -import Data.Monoid (Monoid(..)) import Data.Ratio -import Foreign.Ptr (Ptr, castPtr) -import Foreign.Storable (Storable(..)) -import GHC.Generics import Numeric.Units.Dimensional.Dimensions +import Numeric.Units.Dimensional.Internal import Numeric.Units.Dimensional.UnitNames hiding ((*), (/), (^), weaken, strengthen) import qualified Numeric.Units.Dimensional.UnitNames.Internal as Name -import Numeric.Units.Dimensional.UnitNames.InterchangeNames (HasInterchangeName(..)) import Numeric.Units.Dimensional.Variants hiding (type (*)) import qualified Numeric.Units.Dimensional.Variants as V -import qualified Data.Vector.Generic.Mutable as M -import qualified Data.Vector.Generic as G -import qualified Data.Vector.Unboxed.Base as U {- We will reuse the operators and function names from the Prelude. @@ -299,131 +282,38 @@ -} --- | A physical quantity or unit. --- --- We call this data type 'Dimensional' to capture the notion that the --- units and quantities it represents have physical dimensions. --- --- The type variable 'a' is the only non-phantom type variable and --- represents the numerical value of a quantity or the scale (w.r.t. --- SI units) of a unit. For SI units the scale will always be 1. For --- non-SI units the scale is the ratio of the unit to the SI unit with --- the same physical dimension. --- --- Since 'a' is the only non-phantom type we were able to define --- 'Dimensional' as a newtype, avoiding boxing at runtime. -class KnownVariant (v :: Variant) where - -- | A dimensional value, either a 'Quantity' or a 'Unit', parameterized by its 'Dimension' and representation. - data Dimensional v :: Dimension -> * -> * - extractValue :: Dimensional v d a -> (a, Maybe ExactPi) - extractName :: Dimensional v d a -> Maybe (UnitName 'NonMetric) - injectValue :: (Maybe (UnitName 'NonMetric)) -> (a, Maybe ExactPi) -> Dimensional v d a - -- | Maps over the underlying representation of a dimensional value. - -- The caller is responsible for ensuring that the supplied function respects the dimensional abstraction. - -- This means that the function must preserve numerical values, or linearly scale them while preserving the origin. - dmap :: (a1 -> a2) -> Dimensional v d a1 -> Dimensional v d a2 - -deriving instance Typeable Dimensional - -instance KnownVariant 'DQuantity where - newtype Dimensional 'DQuantity d a = Quantity' a - deriving (Eq, Ord, Data, Generic, Generic1 -#if MIN_VERSION_base(4,8,0) - , Typeable -- GHC 7.8 doesn't support deriving this instance -#endif - ) - extractValue (Quantity' x) = (x, Nothing) - extractName _ = Nothing - injectValue _ (x, _) = Quantity' x - dmap f (Quantity' x) = Quantity' (f x) - -instance (Typeable m) => KnownVariant ('DUnit m) where - data Dimensional ('DUnit m) d a = Unit' !(UnitName m) !ExactPi !a - deriving (Generic, Generic1 -#if MIN_VERSION_base(4,8,0) - , Typeable -- GHC 7.8 doesn't support deriving this instance -#endif - ) - extractValue (Unit' _ e x) = (x, Just e) - extractName (Unit' n _ _) = Just . Name.weaken $ n - injectValue (Just n) (x, Just e) | Just n' <- relax n = Unit' n' e x - | otherwise = Prelude.error "Shouldn't be reachable. Needed a metric name but got a non-metric one." - injectValue _ _ = Prelude.error "Shouldn't be reachable. Needed to name a quantity." - dmap f (Unit' n e x) = Unit' n e (f x) - --- | A unit of measurement. -type Unit (m :: Metricality) = Dimensional ('DUnit m) - --- | A dimensional quantity. -type Quantity = Dimensional 'DQuantity - --- GHC is somewhat unclear about why, but it won't derive this instance, so we give it explicitly. -instance (Bounded a) => Bounded (Quantity d a) where - minBound = Quantity' minBound - maxBound = Quantity' maxBound - -instance HasInterchangeName (Unit m d a) where - interchangeName (Unit' n _ _) = interchangeName n - -- | Extracts the 'UnitName' of a 'Unit'. name :: Unit m d a -> UnitName m -name (Unit' n _ _) = n +name (Unit n _ _) = n -- | Extracts the exact value of a 'Unit', expressed in terms of the SI coherent derived unit (see 'siUnit') of the same 'Dimension'. -- -- Note that the actual value may in some cases be approximate, for example if the unit is defined by experiment. exactValue :: Unit m d a -> ExactPi -exactValue (Unit' _ e _) = e +exactValue (Unit _ e _) = e -- | Discards potentially unwanted type level information about a 'Unit'. weaken :: Unit m d a -> Unit 'NonMetric d a -weaken (Unit' n e v) = Unit' (Name.weaken n) e v +weaken (Unit n e v) = Unit (Name.weaken n) e v -- | Attempts to convert a 'Unit' which may or may not be 'Metric' to one -- which is certainly 'Metric'. strengthen :: Unit m d a -> Maybe (Unit 'Metric d a) -strengthen (Unit' n e v) | Just n' <- Name.strengthen n = Just $ Unit' n' e v - | otherwise = Nothing +strengthen (Unit n e v) | Just n' <- Name.strengthen n = Just $ Unit n' e v + | otherwise = Nothing -- | Forms the exact version of a 'Unit'. exactify :: Unit m d a -> Unit m d ExactPi -exactify (Unit' n e _) = Unit' n e e - --- Operates on a dimensional value using a unary operation on values, possibly yielding a Unit. -liftUntyped :: (KnownVariant v, KnownVariant (Weaken v)) => (ExactPi -> ExactPi) -> (a -> a) -> UnitNameTransformer -> (Dimensional v d1 a) -> (Dimensional (Weaken v) d2 a) -liftUntyped fe f nt x = let (x', e') = extractValue x - n = extractName x - n' = (liftA nt) n - in injectValue n' (f x', fmap fe e') - --- Operates on a dimensional value using a unary operation on values, yielding a Quantity. -liftUntypedQ :: (KnownVariant v) => (a -> a) -> Dimensional v d1 a -> Quantity d2 a -liftUntypedQ f x = let (x', _) = extractValue x - in Quantity' (f x') - --- Combines two dimensional values using a binary operation on values, possibly yielding a Unit. -liftUntyped2 :: (KnownVariant v1, KnownVariant v2, KnownVariant (v1 V.* v2)) => (ExactPi -> ExactPi -> ExactPi) -> (a -> a -> a) -> UnitNameTransformer2 -> Dimensional v1 d1 a -> Dimensional v2 d2 a -> Dimensional (v1 V.* v2) d3 a -liftUntyped2 fe f nt x1 x2 = let (x1', e1') = extractValue x1 - (x2', e2') = extractValue x2 - n1 = extractName x1 - n2 = extractName x2 - n' = (liftA2 nt) n1 n2 - in injectValue n' (f x1' x2', fe <$> e1' <*> e2') - --- Combines two dimensional values using a binary operation on values, yielding a Quantity. -liftUntyped2Q :: (KnownVariant v1, KnownVariant v2) => (a -> a -> a) -> Dimensional v1 d1 a -> Dimensional v2 d2 a -> Quantity d3 a -liftUntyped2Q f x1 x2 = let (x1', _) = extractValue x1 - (x2', _) = extractValue x2 - in Quantity' (f x1' x2') +exactify (Unit n e _) = Unit n e e -- | Forms a 'Quantity' by multipliying a number and a unit. (*~) :: Num a => a -> Unit m d a -> Quantity d a -x *~ (Unit' _ _ y) = Quantity' (x Prelude.* y) +x *~ (Unit _ _ y) = Quantity (x Prelude.* y) -- | Divides a 'Quantity' by a 'Unit' of the same physical dimension, obtaining the -- numerical value of the quantity expressed in that unit. (/~) :: Fractional a => Quantity d a -> Unit m d a -> a -(Quantity' x) /~ (Unit' _ _ y) = (x Prelude./ y) +(Quantity x) /~ (Unit _ _ y) = (x Prelude./ y) {- We give '*~' and '/~' the same fixity as '*' and '/' defined below. @@ -483,9 +373,9 @@ We could provide the 'Mul' and 'Div' classes with full functional dependencies but that would be of limited utility as there is no -obvious use for "backwards" type inference and would also limit -what we can achieve overlapping instances. (In particular, it breaks -the 'Extensible' module.) +limited use for "backwards" type inference. Efforts are underway to +develop a type-checker plugin that does enable these scenarios, e.g. +for linear algebra. -} @@ -504,14 +394,14 @@ -- The intimidating type signature captures the similarity between these operations -- and ensures that composite 'Unit's are 'NonMetric'. (*) :: (KnownVariant v1, KnownVariant v2, KnownVariant (v1 V.* v2), Num a) => Dimensional v1 d1 a -> Dimensional v2 d2 a -> Dimensional (v1 V.* v2) (d1 * d2) a -(*) = liftUntyped2 (Prelude.*) (Prelude.*) (Name.*) +(*) = liftD2 (Prelude.*) (Prelude.*) (Name.*) -- | Divides one 'Quantity' by another or one 'Unit' by another. -- -- The intimidating type signature captures the similarity between these operations -- and ensures that composite 'Unit's are 'NotPrefixable'. (/) :: (KnownVariant v1, KnownVariant v2, KnownVariant (v1 V.* v2), Fractional a) => Dimensional v1 d1 a -> Dimensional v2 d2 a -> Dimensional (v1 V.* v2) (d1 / d2) a -(/) = liftUntyped2 (Prelude./) (Prelude./) (Name./) +(/) = liftD2 (Prelude./) (Prelude./) (Name./) -- | Raises a 'Quantity' or 'Unit' to an integer power. -- @@ -525,7 +415,7 @@ (^) :: (Fractional a, KnownTypeInt i, KnownVariant v, KnownVariant (Weaken v)) => Dimensional v d1 a -> Proxy i -> Dimensional (Weaken v) (d1 ^ i) a x ^ n = let n' = (toNum n) :: Int - in liftUntyped (Prelude.^^ n') (Prelude.^^ n') (Name.^ n') x + in liftD (Prelude.^^ n') (Prelude.^^ n') (Name.^ n') x {- A special case is that dimensionless quantities are not restricted @@ -542,19 +432,19 @@ -- | Negates the value of a 'Quantity'. negate :: Num a => Quantity d a -> Quantity d a -negate = liftUntypedQ Prelude.negate +negate = liftQ Prelude.negate -- | Adds two 'Quantity's. (+) :: Num a => Quantity d a -> Quantity d a -> Quantity d a -(+) = liftUntyped2Q (Prelude.+) +(+) = liftQ2 (Prelude.+) -- | Subtracts one 'Quantity' from another. (-) :: Num a => Quantity d a -> Quantity d a -> Quantity d a -x - y = x + negate y +(-) = liftQ2 (Prelude.-) -- | Takes the absolute value of a 'Quantity'. abs :: Num a => Quantity d a -> Quantity d a -abs = liftUntypedQ Prelude.abs +abs = liftQ Prelude.abs {- Roots of arbitrary (integral) degree. Appears to occasionally be useful @@ -574,7 +464,7 @@ nroot :: (KnownTypeInt n, Floating a) => Proxy n -> Quantity d a -> Quantity (Root d n) a nroot n = let n' = 1 Prelude./ toNum n - in liftUntypedQ (Prelude.** n') + in liftQ (Prelude.** n') {- We provide short-hands for the square and cubic roots. @@ -615,16 +505,6 @@ => Quantity d a -> Proxy n -> Quantity (Root d n) a (^/) = flip nroot -{- -Since quantities form a monoid under addition, but not under multiplication unless they are dimensionless, -we will define a monoid instance that adds. --} - --- | 'Quantity's of a given 'Dimension' form a 'Monoid' under addition. -instance (Num a) => Monoid (Quantity d a) where - mempty = _0 - mappend = (+) - {- $collections Here we define operators and functions to make working with homogenuous lists of dimensionals more convenient. @@ -675,18 +555,6 @@ f i = xi + realToFrac (i % succ n') *~ one * (xf - xi) {- - -= Dimensionless = - -For dimensionless quantities pretty much any operation is applicable. -We provide this freedom by making 'Dimensionless' an instance of -'Functor'. --} - -instance Functor Dimensionless where - fmap = dmap - -{- We continue by defining elementary functions on 'Dimensionless' that may be obviously useful. -} @@ -710,18 +578,12 @@ -- | Raises a dimensionless quantity to a floating power using 'Prelude.**'. (**) :: Floating a => Dimensionless a -> Dimensionless a -> Dimensionless a -(**) = liftUntyped2Q (Prelude.**) +(**) = liftQ2 (Prelude.**) -- | The standard two argument arctangent function. -- Since it interprets its two arguments in comparison with one another, the input may have any dimension. atan2 :: (RealFloat a) => Quantity d a -> Quantity d a -> Dimensionless a -atan2 = liftUntyped2Q Prelude.atan2 - --- | A polymorphic 'Unit' which can be used in place of the coherent --- SI base unit of any dimension. This allows polymorphic quantity --- creation and destruction without exposing the 'Dimensional' constructor. -siUnit :: forall d a.(KnownDimension d, Num a) => Unit 'NonMetric d a -siUnit = Unit' (baseUnitName $ dimension (Proxy :: Proxy d)) 1 1 +atan2 = liftQ2 Prelude.atan2 {- The only unit we will define in this module is 'one'. @@ -734,7 +596,7 @@ -- appear in expressions. However, for us it is necessary to use 'one' -- as we would any other unit to perform the "boxing" of dimensionless values. one :: Num a => Unit 'NonMetric DOne a -one = Unit' nOne 1 1 +one = Unit nOne 1 1 {- $constants For convenience we define some constants for small integer values @@ -747,7 +609,7 @@ -- it to express zero 'Length' or 'Capacitance' or 'Velocity' etc, in addition -- to the 'Dimensionless' value zero. _0 :: Num a => Quantity d a -_0 = Quantity' 0 +_0 = Quantity 0 _1, _2, _3, _4, _5, _6, _7, _8, _9 :: (Num a) => Dimensionless a _1 = 1 *~ one @@ -792,25 +654,6 @@ -} -instance (KnownDimension d) => HasDimension (Dimensional v d a) where - dimension _ = dimension (Proxy :: Proxy d) - -{- -We will conclude by providing a reasonable 'Show' instance for -quantities. The SI unit of the quantity is inferred -from its dimension. --} -instance (KnownDimension d, Show a, Fractional a) => Show (Quantity d a) where - show = showIn siUnit - --- | Shows the value of a 'Quantity' expressed in a specified 'Unit' of the same 'Dimension'. -showIn :: (KnownDimension d, Show a, Fractional a) => Unit m d a -> Quantity d a -> String -showIn (Unit' n _ y) (Quantity' x) | Name.weaken n == nOne = show (x Prelude./ y) - | otherwise = (show (x Prelude./ y)) ++ " " ++ (show n) - -instance (KnownDimension d, Show a) => Show (Unit m d a) where - show (Unit' n e x) = "The unit " ++ show n ++ ", with value " ++ show e ++ " (or " ++ show x ++ ")" - -- | Forms a new atomic 'Unit' by specifying its 'UnitName' and its definition as a multiple of another 'Unit'. -- -- Use this variant when the scale factor of the resulting unit is irrational or 'Approximate'. See 'mkUnitQ' for when it is rational @@ -822,8 +665,8 @@ -- Supplying negative defining quantities is allowed and handled gracefully, but is discouraged -- on the grounds that it may be unexpected by other readers. mkUnitR :: Floating a => UnitName m -> ExactPi -> Unit m1 d a -> Unit m d a -mkUnitR n s' (Unit' _ s x) | isExactZero s = error "Supplying zero as a conversion factor is not valid." - | otherwise = Unit' n (s' Prelude.* s) (approximateValue s' Prelude.* x) +mkUnitR n s' (Unit _ s x) | isExactZero s = error "Supplying zero as a conversion factor is not valid." + | otherwise = Unit n (s' Prelude.* s) (approximateValue s' Prelude.* x) -- | Forms a new atomic 'Unit' by specifying its 'UnitName' and its definition as a multiple of another 'Unit'. -- @@ -832,9 +675,9 @@ -- -- For more information see 'mkUnitR'. mkUnitQ :: Fractional a => UnitName m -> Rational -> Unit m1 d a -> Unit m d a -mkUnitQ n s' (Unit' _ s _) | s' == 0 = error "Supplying zero as a conversion factor is not valid." - | Just q <- toExactRational s'' = Unit' n s'' (fromRational q) - | otherwise = error "The resulting conversion factor is not an exact rational." +mkUnitQ n s' (Unit _ s _) | s' == 0 = error "Supplying zero as a conversion factor is not valid." + | Just q <- toExactRational s'' = Unit n s'' (fromRational q) + | otherwise = error "The resulting conversion factor is not an exact rational." where s'' = fromRational s' Prelude.* s @@ -845,57 +688,8 @@ -- -- For more information see 'mkUnitR'. mkUnitZ :: Num a => UnitName m -> Integer -> Unit m1 d a -> Unit m d a -mkUnitZ n s' (Unit' _ s _) | s' == 0 = error "Supplying zero as a conversion factor is not valid." - | Just z <- toExactInteger s'' = Unit' n s'' (fromInteger z) - | otherwise = error "The resulting conversion factor is not an exact integer." +mkUnitZ n s' (Unit _ s _) | s' == 0 = error "Supplying zero as a conversion factor is not valid." + | Just z <- toExactInteger s'' = Unit n s'' (fromInteger z) + | otherwise = error "The resulting conversion factor is not an exact integer." where s'' = fromInteger s' Prelude.* s - -instance NFData a => NFData (Quantity d a) -- instance is derived from Generic instance - -instance Storable a => Storable (Quantity d a) where - sizeOf _ = sizeOf (undefined::a) - {-# INLINE sizeOf #-} - alignment _ = alignment (undefined::a) - {-# INLINE alignment #-} - poke ptr = poke (castPtr ptr :: Ptr a) . coerce - {-# INLINE poke #-} - peek ptr = liftM Quantity' (peek (castPtr ptr :: Ptr a)) - {-# INLINE peek #-} - -{- -Instances for vectors of quantities. --} -newtype instance U.Vector (Quantity d a) = V_Quantity {unVQ :: U.Vector a} -newtype instance U.MVector s (Quantity d a) = MV_Quantity {unMVQ :: U.MVector s a} -instance U.Unbox a => U.Unbox (Quantity d a) - -instance (M.MVector U.MVector a) => M.MVector U.MVector (Quantity d a) where - basicLength = M.basicLength . unMVQ - {-# INLINE basicLength #-} - basicUnsafeSlice m n = MV_Quantity . M.basicUnsafeSlice m n . unMVQ - {-# INLINE basicUnsafeSlice #-} - basicOverlaps u v = M.basicOverlaps (unMVQ u) (unMVQ v) - {-# INLINE basicOverlaps #-} - basicUnsafeNew = liftM MV_Quantity . M.basicUnsafeNew - {-# INLINE basicUnsafeNew #-} - basicUnsafeRead v = liftM Quantity' . M.basicUnsafeRead (unMVQ v) - {-# INLINE basicUnsafeRead #-} - basicUnsafeWrite v i = M.basicUnsafeWrite (unMVQ v) i . coerce - {-# INLINE basicUnsafeWrite #-} -#if MIN_VERSION_vector(0,11,0) - basicInitialize = M.basicInitialize . unMVQ - {-# INLINE basicInitialize #-} -#endif - -instance (G.Vector U.Vector a) => G.Vector U.Vector (Quantity d a) where - basicUnsafeFreeze = liftM V_Quantity . G.basicUnsafeFreeze . unMVQ - {-# INLINE basicUnsafeFreeze #-} - basicUnsafeThaw = liftM MV_Quantity . G.basicUnsafeThaw . unVQ - {-# INLINE basicUnsafeThaw #-} - basicLength = G.basicLength . unVQ - {-# INLINE basicLength #-} - basicUnsafeSlice m n = V_Quantity . G.basicUnsafeSlice m n . unVQ - {-# INLINE basicUnsafeSlice #-} - basicUnsafeIndexM v = liftM Quantity' . G.basicUnsafeIndexM (unVQ v) - {-# INLINE basicUnsafeIndexM #-}
+ src/Numeric/Units/Dimensional/Coercion.hs view
@@ -0,0 +1,27 @@+{- | + Copyright : Copyright (C) 2006-2014 Bjorn Buckwalter + License : BSD3 + + Maintainer : bjorn@buckwalter.se + Stability : Experimental + Portability: GHC only? + +Re-exports the raw 'Quantity' constructor from the Numeric.Units.Dimensional.Internal module, along with 'Data.Coerce.coerce', +for convenience in converting between raw representations and dimensional values. + +Note that use of these constructs requires the user to verify the dimensional safety of the conversion, +because the coercion doesn't explicitly mention the unit of the representation. + +Note that the haddock documentation doesn't mention the 'Quantity' constructor because it is a part of the +'Dimensional' associated data family, but it is exported by this module. + +-} + +module Numeric.Units.Dimensional.Coercion +( + coerce, Dimensional(Quantity) +) +where + +import Data.Coerce (coerce) +import Numeric.Units.Dimensional.Internal (Dimensional(Quantity))
src/Numeric/Units/Dimensional/Dimensions.hs view
@@ -22,4 +22,4 @@ where import Numeric.Units.Dimensional.Dimensions.TermLevel hiding ((*), (/), (^), recip, dLength, dMass, dTime, dElectricCurrent, dThermodynamicTemperature, dAmountOfSubstance, dLuminousIntensity) -import Numeric.Units.Dimensional.Dimensions.TypeLevel+import Numeric.Units.Dimensional.Dimensions.TypeLevel
src/Numeric/Units/Dimensional/Dynamic.hs view
@@ -16,13 +16,16 @@ module Numeric.Units.Dimensional.Dynamic ( + -- * Dynamic Quantities AnyQuantity , demoteQuantity, promoteQuantity + -- * Dynamic Units , AnyUnit , demoteUnit, promoteUnit ) where import Numeric.Units.Dimensional.Prelude hiding (lookup) +import Numeric.Units.Dimensional.Coercion import Numeric.Units.Dimensional.UnitNames (UnitName, baseUnitName) import Data.ExactPi import Data.Proxy @@ -38,16 +41,16 @@ dimension (AnyQuantity d _) = d -- | Converts a 'Quantity' of statically known 'Dimension' into an 'AnyQuantity'. -demoteQuantity :: forall d v.(KnownDimension d, Fractional v) => Quantity d v -> AnyQuantity v -demoteQuantity val = AnyQuantity dim (val /~ siUnit) +demoteQuantity :: forall d v.(KnownDimension d) => Quantity d v -> AnyQuantity v +demoteQuantity (Quantity val) = AnyQuantity dim val where dim = dimension (Proxy :: Proxy d) -- | Converts an 'AnyQuantity' into a 'Quantity' of statically known 'Dimension', or 'Nothing' if the dimension does not match. -promoteQuantity :: forall d v.(KnownDimension d, Fractional v) => AnyQuantity v -> Maybe (Quantity d v) -promoteQuantity (AnyQuantity dim val) | dim == dim' = Just $ val *~ siUnit +promoteQuantity :: forall d v.(KnownDimension d) => AnyQuantity v -> Maybe (Quantity d v) +promoteQuantity (AnyQuantity dim val) | dim == dim' = Just . Quantity $ val | otherwise = Nothing - where - dim' = dimension (Proxy :: Proxy d) + where + dim' = dimension (Proxy :: Proxy d) -- | A 'Unit' whose 'Dimension' is only known dynamically. data AnyUnit = AnyUnit Dimension' (UnitName 'NonMetric) ExactPi
+ src/Numeric/Units/Dimensional/Internal.hs view
@@ -0,0 +1,237 @@+{-# LANGUAGE CPP #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE MultiParamTypeClasses #-} -- for Vector instances only +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE TypeSynonymInstances #-} + +module Numeric.Units.Dimensional.Internal +( + KnownVariant(..), + Dimensional(..), + type Unit, type Quantity, + siUnit, showIn, + liftD, liftD2, + liftQ, liftQ2 +) +where + +import Control.Applicative +import Control.DeepSeq +import Control.Monad (liftM) +import Data.Coerce (coerce) +import Data.Data +import Data.ExactPi +import Data.Monoid (Monoid(..)) +import Foreign.Ptr (Ptr, castPtr) +import Foreign.Storable (Storable(..)) +import GHC.Generics +import Numeric.Units.Dimensional.Dimensions +import Numeric.Units.Dimensional.Variants hiding (type (*)) +import qualified Numeric.Units.Dimensional.Variants as V +import Numeric.Units.Dimensional.UnitNames hiding ((*), (/), (^), weaken, strengthen) +import qualified Numeric.Units.Dimensional.UnitNames.Internal as Name +import Numeric.Units.Dimensional.UnitNames.InterchangeNames (HasInterchangeName(..)) +import qualified Data.Vector.Generic.Mutable as M +import qualified Data.Vector.Generic as G +import qualified Data.Vector.Unboxed.Base as U +import Prelude + ( Show, Eq(..), Ord, Bounded(..), Num, Fractional, Functor + , String, Maybe(..) + , (.), ($), (++), (+), (/) + , show, otherwise, undefined, error, fmap + ) + +-- | A unit of measurement. +type Unit (m :: Metricality) = Dimensional ('DUnit m) + +-- | A dimensional quantity. +type Quantity = Dimensional 'DQuantity + +-- | A physical quantity or unit. +-- +-- We call this data type 'Dimensional' to capture the notion that the +-- units and quantities it represents have physical dimensions. +-- +-- The type variable 'a' is the only non-phantom type variable and +-- represents the numerical value of a quantity or the scale (w.r.t. +-- SI units) of a unit. For SI units the scale will always be 1. For +-- non-SI units the scale is the ratio of the unit to the SI unit with +-- the same physical dimension. +-- +-- Since 'a' is the only non-phantom type we were able to define +-- 'Dimensional' as a newtype, avoiding boxing at runtime. +class KnownVariant (v :: Variant) where + -- | A dimensional value, either a 'Quantity' or a 'Unit', parameterized by its 'Dimension' and representation. + data Dimensional v :: Dimension -> * -> * + extractValue :: Dimensional v d a -> (a, Maybe ExactPi) + extractName :: Dimensional v d a -> Maybe (UnitName 'NonMetric) + injectValue :: (Maybe (UnitName 'NonMetric)) -> (a, Maybe ExactPi) -> Dimensional v d a + -- | Maps over the underlying representation of a dimensional value. + -- The caller is responsible for ensuring that the supplied function respects the dimensional abstraction. + -- This means that the function must preserve numerical values, or linearly scale them while preserving the origin. + dmap :: (a1 -> a2) -> Dimensional v d a1 -> Dimensional v d a2 + +deriving instance Typeable Dimensional + +instance KnownVariant 'DQuantity where + newtype Dimensional 'DQuantity d a = Quantity a + deriving (Eq, Ord, Data, Generic, Generic1 +#if MIN_VERSION_base(4,8,0) + , Typeable -- GHC 7.8 doesn't support deriving this instance +#endif + ) + extractValue (Quantity x) = (x, Nothing) + extractName _ = Nothing + injectValue _ (x, _) = Quantity x + dmap = coerce + +instance (Typeable m) => KnownVariant ('DUnit m) where + data Dimensional ('DUnit m) d a = Unit !(UnitName m) !ExactPi !a + deriving (Generic, Generic1 +#if MIN_VERSION_base(4,8,0) + , Typeable -- GHC 7.8 doesn't support deriving this instance +#endif + ) + extractValue (Unit _ e x) = (x, Just e) + extractName (Unit n _ _) = Just . Name.weaken $ n + injectValue (Just n) (x, Just e) | Just n' <- relax n = Unit n' e x + | otherwise = error "Shouldn't be reachable. Needed a metric name but got a non-metric one." + injectValue _ _ = error "Shouldn't be reachable. Needed to name a quantity." + dmap f (Unit n e x) = Unit n e (f x) + +-- GHC is somewhat unclear about why, but it won't derive this instance, so we give it explicitly. +instance (Bounded a) => Bounded (Quantity d a) where + minBound = Quantity minBound + maxBound = Quantity maxBound + +instance HasInterchangeName (Unit m d a) where + interchangeName (Unit n _ _) = interchangeName n + +{- +Since quantities form a monoid under addition, but not under multiplication unless they are dimensionless, +we will define a monoid instance that adds. +-} + +-- | 'Quantity's of a given 'Dimension' form a 'Monoid' under addition. +instance (Num a) => Monoid (Quantity d a) where + mempty = Quantity 0 + mappend = liftQ2 (+) + +{- + += Dimensionless = + +For dimensionless quantities pretty much any operation is applicable. +We provide this freedom by making 'Dimensionless' an instance of +'Functor'. +-} + +instance Functor (Quantity DOne) where + fmap = dmap + +instance (KnownDimension d) => HasDimension (Dimensional v d a) where + dimension _ = dimension (Proxy :: Proxy d) + +-- | A polymorphic 'Unit' which can be used in place of the coherent +-- SI base unit of any dimension. This allows polymorphic quantity +-- creation and destruction without exposing the 'Dimensional' constructor. +siUnit :: forall d a.(KnownDimension d, Num a) => Unit 'NonMetric d a +siUnit = Unit (baseUnitName $ dimension (Proxy :: Proxy d)) 1 1 + +instance NFData a => NFData (Quantity d a) -- instance is derived from Generic instance + +instance Storable a => Storable (Quantity d a) where + sizeOf _ = sizeOf (undefined::a) + {-# INLINE sizeOf #-} + alignment _ = alignment (undefined::a) + {-# INLINE alignment #-} + poke ptr = poke (castPtr ptr :: Ptr a) . coerce + {-# INLINE poke #-} + peek ptr = liftM Quantity (peek (castPtr ptr :: Ptr a)) + {-# INLINE peek #-} + +{- +Instances for vectors of quantities. +-} +newtype instance U.Vector (Quantity d a) = V_Quantity {unVQ :: U.Vector a} +newtype instance U.MVector s (Quantity d a) = MV_Quantity {unMVQ :: U.MVector s a} +instance U.Unbox a => U.Unbox (Quantity d a) + +instance (M.MVector U.MVector a) => M.MVector U.MVector (Quantity d a) where + basicLength = M.basicLength . unMVQ + {-# INLINE basicLength #-} + basicUnsafeSlice m n = MV_Quantity . M.basicUnsafeSlice m n . unMVQ + {-# INLINE basicUnsafeSlice #-} + basicOverlaps u v = M.basicOverlaps (unMVQ u) (unMVQ v) + {-# INLINE basicOverlaps #-} + basicUnsafeNew = liftM MV_Quantity . M.basicUnsafeNew + {-# INLINE basicUnsafeNew #-} + basicUnsafeRead v = liftM Quantity . M.basicUnsafeRead (unMVQ v) + {-# INLINE basicUnsafeRead #-} + basicUnsafeWrite v i = M.basicUnsafeWrite (unMVQ v) i . coerce + {-# INLINE basicUnsafeWrite #-} +#if MIN_VERSION_vector(0,11,0) + basicInitialize = M.basicInitialize . unMVQ + {-# INLINE basicInitialize #-} +#endif + +instance (G.Vector U.Vector a) => G.Vector U.Vector (Quantity d a) where + basicUnsafeFreeze = liftM V_Quantity . G.basicUnsafeFreeze . unMVQ + {-# INLINE basicUnsafeFreeze #-} + basicUnsafeThaw = liftM MV_Quantity . G.basicUnsafeThaw . unVQ + {-# INLINE basicUnsafeThaw #-} + basicLength = G.basicLength . unVQ + {-# INLINE basicLength #-} + basicUnsafeSlice m n = V_Quantity . G.basicUnsafeSlice m n . unVQ + {-# INLINE basicUnsafeSlice #-} + basicUnsafeIndexM v = liftM Quantity . G.basicUnsafeIndexM (unVQ v) + {-# INLINE basicUnsafeIndexM #-} + +{- +We will conclude by providing a reasonable 'Show' instance for +quantities. The SI unit of the quantity is inferred +from its dimension. +-} +instance (KnownDimension d, Show a, Fractional a) => Show (Quantity d a) where + show = showIn siUnit + +-- | Shows the value of a 'Quantity' expressed in a specified 'Unit' of the same 'Dimension'. +showIn :: (KnownDimension d, Show a, Fractional a) => Unit m d a -> Quantity d a -> String +showIn (Unit n _ y) (Quantity x) | Name.weaken n == nOne = show (x / y) + | otherwise = (show (x / y)) ++ " " ++ (show n) + +instance (KnownDimension d, Show a) => Show (Unit m d a) where + show (Unit n e x) = "The unit " ++ show n ++ ", with value " ++ show e ++ " (or " ++ show x ++ ")" + +-- Operates on a dimensional value using a unary operation on values, possibly yielding a Unit. +liftD :: (KnownVariant v1, KnownVariant v2) => (ExactPi -> ExactPi) -> (a -> b) -> UnitNameTransformer -> (Dimensional v1 d1 a) -> (Dimensional v2 d2 b) +liftD fe f nt x = let (x', e') = extractValue x + n = extractName x + n' = (liftA nt) n + in injectValue n' (f x', fmap fe e') + +-- Operates on a dimensional value using a unary operation on values, yielding a Quantity. +liftQ :: (a -> a) -> Quantity d1 a -> Quantity d2 a +liftQ = coerce + +-- Combines two dimensional values using a binary operation on values, possibly yielding a Unit. +liftD2 :: (KnownVariant v1, KnownVariant v2, KnownVariant (v1 V.* v2)) => (ExactPi -> ExactPi -> ExactPi) -> (a -> a -> a) -> UnitNameTransformer2 -> Dimensional v1 d1 a -> Dimensional v2 d2 a -> Dimensional (v1 V.* v2) d3 a +liftD2 fe f nt x1 x2 = let (x1', e1') = extractValue x1 + (x2', e2') = extractValue x2 + n1 = extractName x1 + n2 = extractName x2 + n' = (liftA2 nt) n1 n2 + in injectValue n' (f x1' x2', fe <$> e1' <*> e2') + +-- Combines two dimensional values using a binary operation on values, yielding a Quantity. +liftQ2 :: (a -> a -> a) -> Quantity d1 a -> Quantity d2 a -> Quantity d3 a +liftQ2 = coerce
src/Numeric/Units/Dimensional/NonSI.hs view
@@ -165,8 +165,7 @@ several kinds of day), it is best to regard a year as a julian year of 365.25 days (31.5576 Ms) unless otherwise specified. -This aligns well with my needs so I'm happy to oblige. We define -the year in terms of seconds in order to avoid a 'Fractional' +We define the year in terms of seconds in order to avoid a 'Fractional' constraint, and also provide a Julian century. -}