{- Data/Metrology/Qu.hs The units Package Copyright (c) 2013 Richard Eisenberg rae@cs.brynmawr.edu This file defines the 'Qu' type that represents quantity (a number paired with its measurement reference). This file also defines operations on 'Qu's that are shared between the vector and non-vector interfaces. -} {-# LANGUAGE TypeFamilies, TypeOperators, DataKinds, UndecidableInstances, ConstraintKinds, StandaloneDeriving, GeneralizedNewtypeDeriving, FlexibleInstances, RoleAnnotations, FlexibleContexts, ScopedTypeVariables, CPP #-} #if __GLASGOW_HASKELL__ >= 711 {-# OPTIONS_GHC -Wno-redundant-constraints #-} #endif module Data.Metrology.Qu where import Data.Metrology.Dimensions import Data.Metrology.Factor import Data.Metrology.Units import Data.Metrology.Z import Data.Metrology.LCSU import Control.DeepSeq (NFData (..)) import Data.VectorSpace import Text.Read import Data.Coerce ------------------------------------------------------------- --- Internal ------------------------------------------------ ------------------------------------------------------------- -- | 'Qu' adds a dimensional annotation to its numerical value type -- @n@. This is the representation for all quantities. newtype Qu (a :: [Factor *]) (lcsu :: LCSU *) (n :: *) = Qu n type role Qu nominal nominal representational ------------------------------------------------------------- --- User-facing --------------------------------------------- ------------------------------------------------------------- -- Abbreviation for creating a Qu (defined here to avoid a module cycle) -- | Make a quantity type capable of storing a value of a given -- unit. This uses a 'Double' for storage of the value. For example: -- -- > data LengthDim = LengthDim -- > instance Dimension LengthDim -- > data Meter = Meter -- > instance Unit Meter where -- > type BaseUnit Meter = Canonical -- > type DimOfUnit Meter = LengthDim -- > type instance DefaultUnitOfDim LengthDim = Meter -- > type Length = MkQu_D LengthDim -- -- Note that the dimension /must/ have an instance for the type family -- 'DefaultUnitOfDim' for this to work. type MkQu_D dim = Qu (DimFactorsOf dim) DefaultLCSU Double -- | Make a quantity type with a custom numerical type and LCSU. type MkQu_DLN dim = Qu (DimFactorsOf dim) -- | Make a quantity type with a given unit. It will be stored as a 'Double'. -- Note that the corresponding dimension /must/ have an appropriate instance -- for 'DefaultUnitOfDim' for this to work. type MkQu_U unit = Qu (DimFactorsOf (DimOfUnit unit)) DefaultLCSU Double -- | Make a quantity type with a unit and LCSU with custom numerical type. -- The quantity will have the dimension corresponding to the unit. type MkQu_ULN unit = Qu (DimFactorsOf (DimOfUnit unit)) --------------------------------------- --------------------------------------- -- Privileged operations --------------------------------------- --------------------------------------- --------------------------------------- -- Quantities of dimension one --------------------------------------- -- | Convert a raw number into a unitless dimensioned quantity quantity :: n -> Qu '[] l n quantity = Qu --------------------------------------- -- Multiplicative operations --------------------------------------- infixl 7 |*| -- | Multiply two quantities (|*|) :: Num n => Qu a l n -> Qu b l n -> Qu (Normalize (a @+ b)) l n (Qu a) |*| (Qu b) = Qu (a * b) infixl 7 |/| -- | Divide two quantities (|/|) :: Fractional n => Qu a l n -> Qu b l n -> Qu (Normalize (a @- b)) l n (Qu a) |/| (Qu b) = Qu (a / b) --------------------------------------- -- Exponentiation --------------------------------------- -- The following are privileged for efficiency. infixr 8 |^ -- | Raise a quantity to a integer power, knowing at compile time that the integer is non-negative. (|^) :: (NonNegative z, Num n) => Qu a l n -> Sing z -> Qu (a @* z) l n (Qu a) |^ sz = Qu (a ^ szToInt sz) infixr 8 |^^ -- | Raise a quantity to a integer power known at compile time (|^^) :: Fractional n => Qu a l n -> Sing z -> Qu (a @* z) l n (Qu a) |^^ sz = Qu (a ^^ szToInt sz) -- | Take the n'th root of a quantity, where n is known at compile -- time qNthRoot :: ((Zero < z) ~ True, Floating n) => Sing z -> Qu a l n -> Qu (a @/ z) l n qNthRoot sz (Qu a) = Qu (a ** (1.0 / (fromIntegral $ szToInt sz))) --------------------------------------- -- Comparison --------------------------------------- -- | Compare two quantities qCompare :: (d1 @~ d2, Ord n) => Qu d1 l n -> Qu d2 l n -> Ordering qCompare (Qu a) (Qu b) = compare a b infix 4 |<| -- | Check if one quantity is less than a compatible one (|<|) :: (d1 @~ d2, Ord n) => Qu d1 l n -> Qu d2 l n -> Bool (Qu a) |<| (Qu b) = a < b infix 4 |>| -- | Check if one quantity is greater than a compatible one (|>|) :: (d1 @~ d2, Ord n) => Qu d1 l n -> Qu d2 l n -> Bool (Qu a) |>| (Qu b) = a > b infix 4 |<=| -- | Check if one quantity is less than or equal to a compatible one (|<=|) :: (d1 @~ d2, Ord n) => Qu d1 l n -> Qu d2 l n -> Bool (Qu a) |<=| (Qu b) = a <= b infix 4 |>=| -- | Check if one quantity is greater than or equal to a compatible one (|>=|) :: (d1 @~ d2, Ord n) => Qu d1 l n -> Qu d2 l n -> Bool (Qu a) |>=| (Qu b) = a >= b infix 4 |==| -- | Check if two quantities are equal (uses the equality of the underlying numerical type) (|==|) :: (d1 @~ d2, Eq n) => Qu d1 l n -> Qu d2 l n -> Bool (Qu a) |==| (Qu b) = a == b infix 4 |/=| -- | Check if two quantities are not equal (|/=|) :: (d1 @~ d2, Eq n) => Qu d1 l n -> Qu d2 l n -> Bool (Qu a) |/=| (Qu b) = a /= b infix 4 `qApprox` , `qNapprox` -- | Compare two compatible quantities for approximate equality. If the -- difference between the left hand side and the right hand side arguments are -- less than or equal to the /epsilon/, they are considered equal. qApprox :: (d0 @~ d1, d0 @~ d2, Num n, Ord n) => Qu d0 l n -- ^ /epsilon/ -> Qu d1 l n -- ^ left hand side -> Qu d2 l n -- ^ right hand side -> Bool qApprox (Qu epsilon) (Qu a) (Qu b) = abs(a-b) <= epsilon -- | Compare two compatible quantities for approixmate inequality. -- @qNapprox e a b = not $ qApprox e a b@ qNapprox :: (d0 @~ d1, d0 @~ d2, Num n, Ord n) => Qu d0 l n -- ^ /epsilon/ -> Qu d1 l n -- ^ left hand side -> Qu d2 l n -- ^ right hand side -> Bool qNapprox (Qu epsilon) (Qu a) (Qu b) = abs(a-b) > epsilon --------------------------------------- --------------------------------------- -- Unprivileged operations --------------------------------------- --------------------------------------- infixl 7 /| -- | Divide a scalar by a quantity (/|) :: Fractional n => n -> Qu b l n -> Qu (Normalize ('[] @- b)) l n a /| b = quantity a |/| b -- | Square a quantity qSq :: Num n => Qu a l n -> Qu (Normalize (a @+ a)) l n qSq x = x |*| x -- | Cube a quantity qCube :: Num n => Qu a l n -> Qu (Normalize (Normalize (a @+ a) @+ a)) l n qCube x = x |*| x |*| x -- | Take the square root of a quantity qSqrt :: Floating n => Qu a l n -> Qu (a @/ Two) l n qSqrt = qNthRoot sTwo -- | Take the cubic root of a quantity qCubeRoot :: Floating n => Qu a l n -> Qu (a @/ Three) l n qCubeRoot = qNthRoot sThree ------------------------------------------------------------- --- Instances for all quantities ---------------------------- ------------------------------------------------------------- deriving instance Eq n => Eq (Qu d l n) deriving instance Ord n => Ord (Qu d l n) deriving instance NFData n => NFData (Qu d l n) deriving instance AdditiveGroup n => AdditiveGroup (Qu d l n) instance VectorSpace n => VectorSpace (Qu d l n) where type Scalar (Qu d l n) = Scalar n a *^ (Qu b) = Qu (a *^ b) ------------------------------------------------------------- --- Instances for dimensionless quantities ------------------ ------------------------------------------------------------- -- Express the condition on `d` via a constraint, so that the -- requirement for the Num class can inform the choice of -- dimension. See #35. deriving instance (d ~ '[], Num n) => Num (Qu d l n) deriving instance (d ~ '[], Real n) => Real (Qu d l n) deriving instance (d ~ '[], Fractional n) => Fractional (Qu d l n) deriving instance (d ~ '[], Floating n) => Floating (Qu d l n) deriving instance (d ~ '[], RealFrac n) => RealFrac (Qu d l n) deriving instance (d ~ '[], RealFloat n) => RealFloat (Qu d l n) -- But don't do this for Read and Show, because other instances -- are indeed sensible. Using the above technique here would make -- other instances impossible. Also, note that GeneralizedNewtypeDeriving -- puts the "Qu" constructor in Read and Show instances, so don't use -- that. instance Show n => Show (Qu '[] l n) where showsPrec = coerce (showsPrec :: Int -> n -> ShowS) show = coerce (show :: n -> String) showList = coerce (showList :: [n] -> ShowS) instance Read n => Read (Qu '[] l n) where readsPrec = coerce (readsPrec :: Int -> ReadS n) readList = coerce (readList :: ReadS [n]) readPrec = coerce (readPrec :: ReadPrec n) readListPrec = coerce (readListPrec :: ReadPrec [n]) ------------------------------------------------------------- --- Combinators --------------------------------------------- ------------------------------------------------------------- infixl 7 %* -- | Multiply two quantity types to produce a new one. For example: -- -- > type Velocity = Length %/ Time type family (d1 :: *) %* (d2 :: *) :: * type instance (Qu d1 l n) %* (Qu d2 l n) = Qu (d1 @+ d2) l n infixl 7 %/ -- | Divide two quantity types to produce a new one type family (d1 :: *) %/ (d2 :: *) :: * type instance (Qu d1 l n) %/ (Qu d2 l n) = Qu (d1 @- d2) l n infixr 8 %^ -- | Exponentiate a quantity type to an integer type family (d :: *) %^ (z :: Z) :: * type instance (Qu d l n) %^ z = Qu (d @* z) l n ------------------------------------------------------------- --- Term-level combinators ---------------------------------- ------------------------------------------------------------- -- | Use this to choose a default LCSU for a dimensioned quantity. -- The default LCSU uses the 'DefaultUnitOfDim' representation for each -- dimension. defaultLCSU :: Qu dim DefaultLCSU n -> Qu dim DefaultLCSU n defaultLCSU = id -- | The number 1, expressed as a unitless dimensioned quantity. unity :: Num n => Qu '[] l n unity = Qu 1 -- | Cast between equivalent dimension within the same CSU. -- for example [kg m s] and [s m kg]. See the README for more info. redim :: (d @~ e) => Qu d l n -> Qu e l n redim (Qu x) = Qu x -- | The type of unitless dimensioned quantities. -- This is an instance of @Num@, though Haddock doesn't show it. -- This is parameterized by an LCSU and a number representation. type Count = MkQu_ULN Number