packages feed

dimensional (empty) → 0.7

raw patch · 11 files changed

+2214/−0 lines, 11 filesdep +basedep +timebuild-type:Customsetup-changed

Dependencies added: base, time

Files

+ LICENSE view
@@ -0,0 +1,31 @@+Copyright (c) 2006-2007, Bjorn Buckwalter.+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++  * Redistributions of source code must retain the above copyright+    notice, this list of conditions and the following disclaimer.++  * Redistributions in binary form must reproduce the above+    copyright notice, this list of conditions and the following+    disclaimer in the documentation and/or other materials provided+    with the distribution.++  * Neither the name of the copyright holder(s) nor the names of+    contributors may be used to endorse or promote products derived+    from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ Numeric/NumType.lhs view
@@ -0,0 +1,374 @@+Numeric.NumType -- Type level integers+Bjorn Buckwalter, bjorn.buckwalter@gmail.com+License: BSD3+++= Summary =++This Module provides type level representations, hereafter referred+to as "NumTypes", of the (positive and negative) integers and some+basic operations (addition, subtraction...) on these. While functions+are provided for the operations NumTypes are solely for the type+level and their only value is 'undefined'.++There are similarities with the HNats of the HList library [1],+which was indeed a source of inspiration. Occasionally references+are made to the HNats. The main addition in this module is negative+numbers.++The practical size of the NumTypes is limited by the type checker+stack. If the NumTypes grow too large (which can happen quickly+with multiplication) an error message similar to the following will+be emitted:++    Context reduction stack overflow; size = 20 +    Use -fcontext-stack=N to increase stack size to N++This situation could concievably be mitigated significantly by using+e.g. a binary representation of integers rather than Peano numbers.+++= Preliminaries =++This module requires GHC 6.6 or later. We utilize multi-parameter+type classes, phantom types, functional dependencies and undecidable+instances (and possibly additional unidentified GHC extensions).++> {-# LANGUAGE UndecidableInstances, EmptyDataDecls #-}++> module Numeric.NumType +>   -- Basic classes (exported versions).+>   ( NumType, PosType, NegType, NonZero+>   -- Arithmetic classes.+>   , Succ, Negate, Sum, Div, Mul+>   -- Functions.+>   , toNum, incr, decr, negate, (+), (-), (*), (/)+>   -- Data types.+>   , Zero, Pos, Neg+>   -- Type synonyms for convenience.+> 	, Pos1, Pos2, Pos3, Pos4, Pos5, Neg1, Neg2, Neg3, Neg4, Neg5+>   -- Values for convenience.+> 	, zero, pos1, pos2, pos3, pos4, pos5, neg1, neg2, neg3, neg4, neg5+>   ) where++> import Prelude hiding ((*), (/), (+), (-), negate)+> import qualified Prelude ((+), (-))++Use the same fixity for operators as the Prelude.++> infixl 7  *, /+> infixl 6  +, -+++= NumTypes =++We start by defining a class encompassing all integers with the+class function 'toNum' that converts from the type-level to a+value-level 'Num'.++> class NumTypeI n where toNum :: (Num a) => n -> a++Then we define classes encompassing all positive and negative+integers respectively. The 'PosTypeI' class corresponds to HList's+'HNat'. We also define a class for non-zero numbers (used to+prohibit division by zero).++> class (NumTypeI n) => PosTypeI n+> class (NumTypeI n) => NegTypeI n+> class (NumTypeI n) => NonZeroI n++Now we use a trick from Oleg Kiselyov and Chung-chieh Shan [2]:++    -- The well-formedness condition, the kind predicate+    class Nat0 a where toInt :: a -> Int+    class Nat0 a => Nat a           -- (positive) naturals++    -- To prevent the user from adding new instances to Nat0 and especially+    -- to Nat (e.g., to prevent the user from adding the instance |Nat B0|)+    -- we do NOT export Nat0 and Nat. Rather, we export the following proxies.+    -- The proxies entail Nat and Nat0 and so can be used to add Nat and Nat0+    -- constraints in the signatures. However, all the constraints below+    -- are expressed in terms of Nat0 and Nat rather than proxies. Thus,+    -- even if the user adds new instances to proxies, it would not matter.+    -- Besides, because the following proxy instances are most general,+    -- one may not add further instances without overlapping instance extension.+    class    Nat0 n => Nat0E n+    instance Nat0 n => Nat0E n+    class    Nat n => NatE n+    instance Nat n => NatE n++We apply this trick to our classes. In our case we will elect to+append an "I" to the internal (non-exported) classes rather than+appending an "E" to the exported classes.++> class    (NumTypeI n) => NumType n+> instance (NumTypeI n) => NumType n+> class    (PosTypeI n) => PosType n+> instance (PosTypeI n) => PosType n+> class    (NegTypeI n) => NegType n+> instance (NegTypeI n) => NegType n+> class    (NonZeroI n) => NonZero n+> instance (NonZeroI n) => NonZero n++We do not have to do this for our other classes. They have the above+classes in their constraints and since the instances are complete+(not proven) a new instance cannot be defined (actually used in the+case of GHC) without overlapping instances.++Now we Define the data types used to represent integers. We begin+with 'Zero', which we allow to be used as both a positive and a+negative number in the sense of the previously defined type classes.+'Zero' corresponds to HList's 'HZero'.++> data Zero+> instance NumTypeI Zero where toNum _ = 0+> instance PosTypeI Zero+> instance NegTypeI Zero++Next we define the "successor" type, here called 'Pos' (corresponding+to HList's 'HSucc').++> data Pos n+> instance (PosTypeI n) => NumTypeI (Pos n) where +>   toNum _ = toNum (undefined :: n) Prelude.+ 1 +> instance (PosTypeI n) => PosTypeI (Pos n)+> instance (PosTypeI n) => NonZeroI (Pos n)++We could be more restrictive using "data (PosTypeI n) => Pos n" but+this constraint will not be checked (by GHC) anyway when 'Pos' is+used solely at the type level. ++Finally we define the "predecessor" type used to represent negative+numbers.++> data Neg n+> instance (NegTypeI n) => NumTypeI (Neg n) where+>   toNum _ = toNum (undefined :: n) Prelude.- 1 +> instance (NegTypeI n) => NegTypeI (Neg n)+> instance (NegTypeI n) => NonZeroI (Neg n)+ ++= Show instances =++For convenience we create show instances for the defined NumTypes.++> instance Show Zero where show _ = "NumType 0"+> instance (PosTypeI n) => Show (Pos n) where show x = "NumType " ++ show (toNum x)+> instance (NegTypeI n) => Show (Neg n) where show x = "NumType " ++ show (toNum x)++ += Negation, incrementing and decrementing =++We start off with some basic building blocks. Negation is a simple+matter of recursively changing 'Pos' to 'Neg' or vice versa while+leaving 'Zero' unchanged.++> class (NumTypeI a, NumTypeI b) => Negate a b | a -> b, b -> a++> instance Negate Zero Zero+> instance (PosTypeI a, NegTypeI b, Negate a b) => Negate (Pos a) (Neg b)+> instance (NegTypeI a, PosTypeI b, Negate a b) => Negate (Neg a) (Pos b) ++We define a type class for incrementing and decrementing NumTypes.+The 'incr' and 'decr' functions correspond roughly to HList's 'hSucc'+and 'hPred' respectively.++> class (NumTypeI a, NumTypeI b) => Succ a b | a -> b, b -> a++To increment NumTypes we either prepend 'Pos' to numbers greater+than or equal to Zero or remove a 'Neg' from numbers less than Zero.++> instance Succ Zero (Pos Zero)+> instance (PosTypeI a) => Succ (Pos a) (Pos (Pos a))+> instance Succ (Neg Zero) Zero+> instance (NegTypeI a) => Succ (Neg (Neg a)) (Neg a)+++= Addition and subtraction =++Now let us move on towards more complex arithmetic operations. We+define a class for addition and subtraction of NumTypes.++> class (Add a b c, Sub c b a)+>    => Sum a b c | a b -> c, a c -> b, b c -> a++In order to provide instances satisfying the functional dependencies+of 'Sum', in particular the property that any two parameters uniquely+define the third, we must use helper classes.++> class (NumTypeI a, NumTypeI b, NumTypeI c) => Add a b c | a b -> c+> class (NumTypeI a, NumTypeI b, NumTypeI c) => Sub a b c | a b -> c++Adding anything to Zero gives "anything".++> instance (NumTypeI a) => Add Zero a a++When adding to a non-Zero number our strategy is to "transfer" type+constructors from the first type to the second type until the first+type is Zero. We use the 'Succ' class to do this.++> instance (PosTypeI a, Succ b c, Add a c d) => Add (Pos a) b d+> instance (NegTypeI a, Succ c b, Add a c d) => Add (Neg a) b d++We define our helper class for subtraction analogously.++> instance (NumType a) => Sub a Zero a+> instance (Succ a' a, PosTypeI b, Sub a' b c) => Sub a (Pos b) c+> instance (Succ a a', NegTypeI b, Sub a' b c) => Sub a (Neg b) c++While we cold have defined a single 'Sub' instance using negation and+addition.++] instance (Negate b b', Add a b' c) => Sub a b c++However, the constraints of such a 'Sub' instance which are not+also constraints of the 'Sub' class can complicate type signatures+(is this true or was I confused by other issues at the time?). Thus+we elect to use the lower level instances analoguous to the 'Add'+instances.++Using the helper classes we can provide an instance of 'Sum' that+satisfies its functional dependencies. We provide an instance of+'Sum' in terms of 'Add' and 'Sub'.++> instance (Add a b c, Sub c b a) => Sum a b c+++= Division =++We will do division on NumTypes before we do multiplication. This+may be surprising but it will in fact simplify the multiplication.+The reason for this is that we can have a "reverse" functional+dependency for division but not for multiplication. Consider the+expressions "x / y = z". If y and z are known we can always determine+x. However, in "x * y = z" we can not determine x if y and z are+zero.++The 'NonZeroI' class is used as a constraint on the denominator 'b'+in our 'Div' class.++> class (NumTypeI a, NonZeroI b, NumTypeI c) => Div a b c | a b -> c, c b -> a++Zero divided by anything (we don't bother with infinity) equals+zero.++> instance (NonZeroI n) => Div Zero n Zero++Note that We could omit the 'NonZeroI' class completely and instead+provide the following two instances.++] instance (PosTypeI n) => Div Zero (Pos n) Zero+] instance (NegTypeI n) => Div Zero (Neg n) Zero++Going beyond zero numbers we start with a base case with all numbers+positive. We recursively subtract the denominator from nominator+while incrementing the result, until we reach the zero case.++> instance ( Sum n' (Pos n'') (Pos n)+>          , Div n'' (Pos n') n''', PosTypeI n''') +>       => Div (Pos n) (Pos n') (Pos n''')++Now we tackle cases with negative numbers involved. We trivially+convert these to the all-positive case and negate the result if+appropriate.++> instance ( NegTypeI n, NegTypeI n'+>          , Negate n p, Negate n' p'+>          , Div (Pos p) (Pos p') (Pos p''))+>       => Div (Neg n) (Neg n') (Pos p'')+> instance ( NegTypeI n, Negate n p'+>          , Div (Pos p) (Pos p') (Pos p'')+>          , Negate (Pos p'') (Neg n''))+>       => Div (Pos p) (Neg n) (Neg n'')+> instance ( NegTypeI n, Negate n p'+>          , Div (Pos p') (Pos p) (Pos p'')+>          , Negate (Pos p'') (Neg n''))+>       => Div (Neg n) (Pos p) (Neg n'')+++= Multiplication =++Class for multiplication. Limited by the type checker stack. If the+multiplication is too large this error message will be emitted:++    Context reduction stack overflow; size = 20 +    Use -fcontext-stack=N to increase stack size to N++> class (NumTypeI a, NumTypeI b, NumTypeI c) => Mul a b c | a b -> c++Providing instances for the 'Mul' class is really easy thanks to+the 'Div' class having the functional dependency "c b -> a".++> instance (NumTypeI n) => Mul n Zero Zero+> instance (PosTypeI p, Div c (Pos p) a) => Mul a (Pos p) c+> instance (NegTypeI n, Div c (Neg n) a) => Mul a (Neg n) c+++= Functions =++Using the above type classes we define functions for various+arithmetic operations. All functions are undefined and only operate+on the type level. Their main contribution is that they facilitate+NumType arithmetic without explicit (and tedious) type declarations.++The main reason to collect all functions here is to keep the+preceeding sections free from distraction.++> negate :: (Negate a b) => a -> b+> negate _ = undefined++> incr :: (Succ a b) => a -> b+> incr _ = undefined+> decr :: (Succ a b) => b -> a+> decr _ = undefined++> (+) :: (Sum a b c) => a -> b -> c+> _ + _ = undefined+> (-) :: (Sum a b c) => c -> b -> a+> _ - _ = undefined++> (/) :: (Div a b c) => a -> b -> c +> _ / _ = undefined++> (*) :: (Mul a b c) => a -> b -> c +> _ * _ = undefined+++= Convenince types and values =++Finally we define some type synonyms for the convenience of clients+of the library.++> type Pos1 = Pos Zero+> type Pos2 = Pos Pos1+> type Pos3 = Pos Pos2+> type Pos4 = Pos Pos3+> type Pos5 = Pos Pos4+> type Neg1 = Neg Zero+> type Neg2 = Neg Neg1+> type Neg3 = Neg Neg2+> type Neg4 = Neg Neg3+> type Neg5 = Neg Neg4++Analogously we also define some convenience values (all 'undefined'+but with the expected types).++> zero :: Zero  -- ~ hZero+> zero = undefined+> pos1 = incr zero+> pos2 = incr pos1+> pos3 = incr pos2+> pos4 = incr pos3+> pos5 = incr pos4+> neg1 = decr zero+> neg2 = decr neg1+> neg3 = decr neg2+> neg4 = decr neg3+> neg5 = decr neg4+++= References =++[1] http://homepages.cwi.nl/~ralf/HList/+[2] http://okmij.org/ftp/Computation/resource-aware-prog/BinaryNumber.hs+
+ Numeric/Units/Dimensional.lhs view
@@ -0,0 +1,622 @@+Numeric.Dimensional -- Statically checked physical dimensions+Bjorn Buckwalter, bjorn.buckwalter@gmail.com+License: BSD3+++= Summary =++In this module we provide data types for performing arithmetic with+physical quantities and units. Information about the physical+dimensions of the quantities/units is embedded in their types and+the validity of operations is verified by the type checker at compile+time. The boxing and unboxing of numerical values as quantities is+done by multiplication and division of units, of which an incomplete+set is provided.++We limit ourselves to "Newtonian" physics. We do not attempt to+accommodate relativistic physics in which e.g. addition of length+and time would be valid.++As far as possible and/or practical the conventions and guidelines+of NIST's "Guide for the Use of the International System of Units+(SI)" [1] are followed. Occasionally we will reference specific+sections from the guide and deviations will be explained.+++= Disclaimer =++Merely an engineer, the author doubtlessly uses a language and+notation that makes mathematicians and physicist cringe. He does+not mind constructive criticism (or darcs patches).++The sets of functions and units defined herein are incomplete and+reflect only the author's needs to date. Again, patches are welcome.++The author has elected to keep the module detached from the standard(?)+Haskell library hierarchy. In part because the module name space+layout seems to be an open issue and in part because he is unsure+where to fit it in.+++= Preliminaries =++This module requires GHC 6.6 or later. We utilize multi-parameter+type classes, phantom types, functional dependencies and undecidable+instances (and possibly additional unidentified GHC extensions).+Clients of the module are generally not required to use these+extensions.++> {-# LANGUAGE UndecidableInstances, ScopedTypeVariables #-}+>   -- Does 'ScopedTypeVariables' imply 'EmptyDataDecls'?++> module Numeric.Units.Dimensional +>       -- TODO discriminate exports, in particular Variants and Dims.+>   where++> import Prelude +>   ( Show, Eq, Ord, Num, Fractional, Floating, RealFloat, Functor, fmap+>   , (.), flip, show, (++), undefined, otherwise, (==), String, unwords+>   , map, foldr+>   )+> import qualified Prelude +> import Data.List (genericLength)+> import Data.Maybe (Maybe (Just, Nothing), catMaybes)+> import Numeric.NumType +>   ( NumType, NonZero, PosType, Zero, toNum, Sum+>   , Pos1, Pos2, pos2, Pos3, pos3+>   , neg3, zero -- Only for playing around.+>   )+> import qualified Numeric.NumType as N (Mul, Div)++We will reuse the operators and function names from the Prelude.+To prevent unpleasant surprises we give operators the same fixity+as the Prelude.++> infixr 8  ^, ^+, ^/, **+> infixl 7  *, /+> infixl 6  +, -+++= Dimensional =++Our primary objective is to define a data type that can be used to+represent (while still differentiating between) units and quantities.+There are two reasons for consolidating units and quantities in one+data type. The first being to allow code reuse as they are largely+subject to the same operations. The second being that it allows+reuse of operators (and functions) between the two without resorting+to occasionally cumbersome type classes.++We call this data type 'Dimensional' to capture the notion that the+units and quantities it represents have physical dimensions.++> newtype Dimensional v d a = Dimensional a deriving (Eq, Ord)++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.+++= The variety 'v' of 'Dimensional' =++The phantom type variable v is used to distinguish between units+and quantities. It should be one of the following:++> data DUnit+> data DQuantity++For convenience we define type synonyms for units and quantities.++> type Unit     = Dimensional DUnit+> type Quantity = Dimensional DQuantity++The relationship between (the value of) a 'Quantity', its numerical+value and its 'Unit' is described in 7.1 "Value and numerical value+of a quantity" of [1]. In short a 'Quantity' is the product of a+number and a 'Unit'. We define the '(*~)' operator as a convenient+way to declare quantities as such a product.++> (*~) :: Num a => a -> Unit d a -> Quantity d a+> x *~ Dimensional y = Dimensional (x Prelude.* y)++Conversely, the numerical value of a 'Quantity' is obtained by+dividing the 'Quantity' by its 'Unit' (any unit with the same+physical dimension). The '(/~)' operator provides a convenient way+of obtaining the numerical value of a quantity.++> (/~) :: Fractional a => Quantity d a -> Unit d a -> a+> Dimensional x /~ Dimensional y = x Prelude./ y++We give '*~' and '/~' the same fixity as '*' and '/' defined below.+Note that this necessitates the use of parenthesis when composing +units using '*' and '/', e.g. "1 *~ (meter / second)".++> infixl 7  *~, /~+++= The dimension 'd' of 'Dimensional' =++The phantom type variable d encompasses the physical dimension of+the 'Dimensional'. As detailed in [5] there are seven base dimensions,+which can be combined in integer powers to a given physical dimension.+We represent physical dimensions as the powers of the seven base+dimensions that make up the given dimension. The powers are represented+using NumTypes. For convenience we collect all seven base dimensions+in a data type 'Dim'.++> data Dim l m t i th n j ++where the respective dimensions are represented by type variables+using the following convention.++    l  -- Length+    m  -- Mass+    t  -- Time+    i  -- Electric current+    th -- Thermodynamic temperature+    n  -- Amount of substance+    j  -- Luminous intensity++We could have chosen to provide type variables for the seven base+dimensions in 'Dimensional' instead of creating a new data type+'Dim'. However, that would have made any type signatures involving+'Dimensional' very cumbersome.  By encompassing the physical dimension+in a single type variable we can "hide" the cumbersome type arithmetic+behind convenient type classes as will be seen later.++Using our 'Dim' data type we define some type synonyms for convenience+and illustrative purposes. We start with the base dimensions.++> type DOne         = Dim Zero Zero Zero Zero Zero Zero Zero+> type DLength      = Dim Pos1 Zero Zero Zero Zero Zero Zero+> type DMass        = Dim Zero Pos1 Zero Zero Zero Zero Zero+> type DTime        = Dim Zero Zero Pos1 Zero Zero Zero Zero+> type DElectricCurrent          = Dim Zero Zero Zero Pos1 Zero Zero Zero+> type DThermodynamicTemperature = Dim Zero Zero Zero Zero Pos1 Zero Zero+> type DAmountOfSubstance        = Dim Zero Zero Zero Zero Zero Pos1 Zero+> type DLuminousIntensity        = Dim Zero Zero Zero Zero Zero Zero Pos1++Using the above type synonyms we can define type synonyms for+quantities of particular physical dimensions.++Quantities with the base dimensions.++> type Dimensionless            = Quantity DOne+> type Length                   = Quantity DLength+> type Mass                     = Quantity DMass+> type Time                     = Quantity DTime+> type ElectricCurrent          = Quantity DElectricCurrent+> type ThermodynamicTemperature = Quantity DThermodynamicTemperature+> type AmountOfSubstance        = Quantity DAmountOfSubstance+> type LuminousIntensity        = Quantity DLuminousIntensity+++= Arithmetic on physical dimensions =++When performing arithmetic on units and quantities the arithmetics+must be applied to both the numerical values of the Dimensionals+but also to their physical dimensions. The type level arithmetic+on physical dimensions is governed by multi-parameter type classes+and functional dependences.++Multiplication of dimensions corresponds to adding of the base+dimensions' exponents.++> class Mul d d' d'' | d d' -> d''+> instance (Sum l  l'  l'',+>           Sum m  m'  m'',+>           Sum t  t'  t'',+>           Sum i  i'  i'',+>           Sum th th' th'',+>           Sum n  n'  n'',+>           Sum j  j'  j'') => Mul (Dim l   m   t   i   th   n   j)+>                                  (Dim l'  m'  t'  i'  th'  n'  j')+>                                  (Dim l'' m'' t'' i'' th'' n'' j'')++Division of dimensions corresponds to subtraction of the base+dimensions' exponents.++> class Div d d' d'' | d d' -> d''+> instance (Sum l  l'  l'',+>           Sum m  m'  m'',+>           Sum t  t'  t'',+>           Sum i  i'  i'',+>           Sum th th' th'',+>           Sum n  n'  n'',+>           Sum j  j'  j'') => Div (Dim l'' m'' t'' i'' th'' n'' j'')+>                                  (Dim l'  m'  t'  i'  th'  n'  j')+>                                  (Dim l   m   t   i   th   n   j)++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.)++We limit ourselves to integer powers of Dimensionals as fractional+powers make little physical sense. Since the value of the exponent+affects the type of the result the value of the exponent must be+visible to the type system, therefore we will generally represent+the exponent with a 'NumType'. ++Powers of dimensions corresponds to multiplication of the base+dimensions' exponents by the exponent.++> class (NumType x) => Pow d x d' | d x -> d'+> instance (N.Mul l  x l',+>           N.Mul m  x m',+>           N.Mul t  x t',+>           N.Mul i  x i',+>           N.Mul th x th',+>           N.Mul n  x n',+>           N.Mul j  x j') => Pow (Dim l  m  t  i  th  n  j) x +>                                 (Dim l' m' t' i' th' n' j')++Roots of dimensions corresponds to division of the base dimensions'+exponents by order(?) of the root.++> class (NonZero x) => Root d x d' | d x -> d'+> instance (N.Div l  x l',+>           N.Div m  x m',+>           N.Div t  x t',+>           N.Div i  x i',+>           N.Div th x th',+>           N.Div n  x n',+>           N.Div j  x j') => Root (Dim l  m  t  i  th  n  j) x +>                                  (Dim l' m' t' i' th' n' j')+++= Arithmetic on units and quantities =++Thanks to the arithmetic on physical dimensions having been sorted+out separately a lot of the arithmetic on Dimensionals is straight+forward. In particular the type signatures are much simplified.++Multiplication, division and powers apply to both units and quantities.++> (*) :: (Num a, Mul d d' d'') +>     => Dimensional v d a -> Dimensional v d' a -> Dimensional v d'' a+> Dimensional x * Dimensional y = Dimensional (x Prelude.* y)++> (/) :: (Fractional a, Div d d' d'') +>     => Dimensional v d a -> Dimensional v d' a -> Dimensional v d'' a+> Dimensional x / Dimensional y = Dimensional (x Prelude./ y)++> (^) :: (Fractional a, Pow d n d')+>     => Dimensional v d a -> n -> Dimensional v d' a+> Dimensional x ^ n = Dimensional (x Prelude.^^ toNum n)++In the unlikely case someone needs to use this library with+non-fractional numbers we provide the alternative power operator+'^+' that is restricted to positive exponents.++> (^+) :: (Num a, PosType n, Pow d n d')+>      => Dimensional v d a -> n -> Dimensional v d' a+> Dimensional x ^+ n = Dimensional (x Prelude.^ toNum n)++A special case is that dimensionless quantities are not restricted+to integer exponents. This is accommodated by the '**' operator+defined later.+++= Quantity operations =++Some additional operations obviously only make sense for quantities.+Of these, negation, addition and subtraction are particularly simple+as they are done in a single physical dimension.++> negate :: (Num a) => Quantity d a -> Quantity d a+> negate (Dimensional x) = Dimensional (Prelude.negate x)++> (+) :: (Num a) => Quantity d a -> Quantity d a -> Quantity d a+> Dimensional x + Dimensional y = Dimensional (x Prelude.+ y)++> (-) :: (Num a) => Quantity d a -> Quantity d a -> Quantity d a+> x - y = x + negate y++Absolute value.++> abs :: (Num a) => Quantity d a -> Quantity d a+> abs (Dimensional x) = Dimensional (Prelude.abs x)++Roots of arbitrary (integral) degree. Appears to occasionally be useful+for units as well as quantities.++> nroot :: (Floating a, Root d n d') => n -> Dimensional v d a -> Dimensional v d' a+> nroot n (Dimensional x) = Dimensional (x Prelude.** (1 Prelude./ toNum n))++We provide short-hands for the square and cubic roots.++> sqrt :: (Floating a, Root d Pos2 d') => Dimensional v d a -> Dimensional v d' a+> sqrt = nroot pos2+> cbrt :: (Floating a, Root d Pos3 d') => Dimensional v d a -> Dimensional v d' a+> cbrt = nroot pos3++We also provide an operator alternative to nroot for those that+prefer such.++> (^/) :: (Floating a, Root d n d') => Dimensional v d a -> n -> Dimensional v d' a+> (^/) = flip nroot+++= List functions =++Here we define operators and functions to make working with homogenuous+lists of dimensionals more convenient.++We define two convenience operators for applying units to all+elements of a list.++> (*~~) :: Num a => [a] -> Unit d a -> [Quantity d a]+> xs *~~ u = map (*~ u) xs++> (/~~) :: Fractional a => [Quantity d a] -> Unit d a -> [a]+> xs /~~ u = map (/~ u) xs++The sum of all elements in a list.++> sum :: forall d a . Num a => [Quantity d a] -> Quantity d a+> sum = foldr (+) (Dimensional 0 :: Quantity d a)++The length of the list as a 'Dimensionless'. This can be useful for+purposes of e.g. calculating averages.++> dimensionlessLength :: Num a => [Dimensional v d a] -> Dimensionless a+> dimensionlessLength = Dimensional . genericLength+++= 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 f (Dimensional x) = Dimensional (f x)++We continue by defining elementary functions on 'Dimensionless'+that may be obviously useful. ++> exp, log, sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh +>   :: (Floating a) => Dimensionless a -> Dimensionless a+> exp   = fmap Prelude.exp+> log   = fmap Prelude.log+> sin   = fmap Prelude.sin+> cos   = fmap Prelude.cos+> tan   = fmap Prelude.tan+> asin  = fmap Prelude.asin+> acos  = fmap Prelude.acos+> atan  = fmap Prelude.atan+> sinh  = fmap Prelude.sinh+> cosh  = fmap Prelude.cosh+> tanh  = fmap Prelude.tanh+> asinh = fmap Prelude.asinh+> acosh = fmap Prelude.acosh+> atanh = fmap Prelude.atanh++> (**) :: (Floating a) +>      => Dimensionless a -> Dimensionless a -> Dimensionless a+> Dimensional x ** Dimensional y = Dimensional (x Prelude.** y)++For 'atan2' the operands need not be dimensionless but they must be+of the same type. The result will of course always be dimensionless.++> atan2 :: (RealFloat a) +>       => Quantity d a -> Quantity d a -> Dimensionless a+> atan2 (Dimensional y) (Dimensional x) = Dimensional (Prelude.atan2 y x)++The only unit we will define in this module is 'one'. The unit one+has dimension one and is the base unit of dimensionless values. As+detailed in 7.10 "Values of quantities expressed simply as numbers:+the unit one, symbol 1" of [1] the unit one generally does not+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 DOne a+> one = Dimensional 1++For convenience We define some constants for small integer values+that often show up in formulae. We also throw in 'pi' for good+measure.++> _0, _1, _2, _3, _4 :: (Num a) => Dimensionless a+> _0 = 0 *~ one+> _1 = 1 *~ one+> _2 = 2 *~ one+> _3 = 3 *~ one+> _4 = 4 *~ one++> pi :: (Floating a) => Dimensionless a+> pi = Prelude.pi *~ one+++= Instances of 'Show' =++We will conclude by providing a reasonable 'Show' instance for+quantities. We neglect units since it is unclear how to represent them+in a way that distinguishes them from quantities, or whether that is+even a requirement.++> instance forall d a. (Show d, Show a) => Show (Quantity d a) where+>   show (Dimensional x) = show x ++ " " ++ show (undefined :: d)++The above implementation of 'show' relies on the dimension 'd' being an+instance of 'Show'. The "normalized" unit of the quantity can be inferred+from its dimension.++> instance forall l m t i th n j.+>   ( NumType l+>   , NumType m+>   , NumType t+>   , NumType i+>   , NumType th+>   , NumType n+>   , NumType j+>   ) => Show (Dim l m t i th n j) where+>   show _ = (unwords . catMaybes)+>            [ dimUnit "m"   (undefined :: l)+>            , dimUnit "kg"  (undefined :: m)+>            , dimUnit "s"   (undefined :: t)+>            , dimUnit "A"   (undefined :: i)+>            , dimUnit "K"   (undefined :: th)+>            , dimUnit "mol" (undefined :: n)+>            , dimUnit "cd"  (undefined :: j)+>            ]++The helper function 'dimUnit' defined next conditions a 'String' (unit)+with an exponent, if appropriate. The reason we define 'dimUnit' at the+top-level rather than in the where-clause is that it may be useful for+users of the 'Extensible' module.++> dimUnit :: (NumType n) => String -> n -> Maybe String+> dimUnit u n +>   | x == 0    = Nothing+>   | x == 1    = Just u+>   | otherwise = Just (u ++ "^" ++ show x)+>   where x = toNum n+++= The 'prefix' function =++We will define a 'prefix' function which applies a scale factor to+a unit. The 'prefix' function will be used by other modules to+define the SI prefixes and non-SI units.++> prefix :: (Num a) => a -> Unit d a -> Unit d a+> prefix x (Dimensional y) = Dimensional (x Prelude.* y)+++= Conclusion and usage =++We have defined operators and units that allow us to define and+work with physical quantities. A physical quantity is defined by+multiplying a number with a unit (the type signature is optional).++] v :: Velocity Prelude.Double+] v = 90 *~ (kilo meter / hour)++It follows naturally that the numerical value of a quantity is+obtained by division by a unit.++] numval :: Prelude.Double+] numval = v /~ (meter / second)+ +The notion of a quantity as the product of a numerical value and a+unit is supported by 7.1 "Value and numerical value of a quantity" of+[1]. While the above syntax is fairly natural it is unfortunate that+it must violate a number of the guidelines in [1], in particular 9.3+"Spelling unit names with prefixes", 9.4 "Spelling unit names obtained+by multiplication", 9.5 "Spelling unit names obtained by division".++As a more elaborate example of how to use the module we define a+function for calculating the escape velocity of a celestial body+[2].++] escapeVelocity :: (Floating a) => Mass a -> Length a -> Velocity a+] escapeVelocity m r = sqrt (two * g * m / r)+]   where +]       two = 2 *~ one+]       g = 6.6720e-11 *~ (newton * meter ^ pos2 / kilo gram ^ pos2)++The following is an example GHC session where the above function+is used to calculate the escape velocity of Earth in kilometer per+second.++  *Numeric.Dimensional> :set +t+  *Numeric.Dimensional> let me = 5.9742e24 *~ kilo gram -- Mass of Earth.+  me :: Quantity DMass GHC.Float.Double+  *Numeric.Dimensional> let re = 6372.792 *~ kilo meter -- Mean radius of Earth.+  re :: Quantity DLength GHC.Float.Double+  *Numeric.Dimensional> let ve = escapeVelocity me re   -- Escape velocity of Earth.+  ve :: Velocity GHC.Float.Double+  *Numeric.Dimensional> ve /~ (kilo meter / second)+  11.184537332296259+  it :: GHC.Float.Double++For completeness we should also show an example of the error messages+we will get from GHC when performing invalid arithmetic. In the+best case GHC will be able to use the type synonyms we have defined+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+    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 Numeric/Dimensional.lhs:425:9-20+      Sub Zero (Pos Zero) Zero,+        arising from use of `/' at Numeric/Dimensional.lhs:532:5-30++It is the author's experience that the usefullness of the compiler+error messages is more often than not limited to pinpointing the+location of errors.+++= Future work =++While there is an insane amount of units in use around the world+it is reasonable to provide at least all SI units. Units outside+of SI will most likely be added on an as-needed basis. ++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 [3] for ideas.+++= Related work =++Henning Thielemann numeric prelude has a physical units library,+however, checking of dimensions is dynamic rather than static.+Aaron Denney has created a toy example of statically checked+physical dimensions covering only length and time. HaskellWiki+has pointers [4] to these.++Also see Samuel Hoffstaetter's blog post [5] which uses techniques+similar to this library.++Libraries with similar functionality exist for other programming+languages and may serve as inspiration. The author has found the+Java library JScience [6] and the Fortress programming language [7]+particularly noteworthy.+++= References =++[1] http://physics.nist.gov/Pubs/SP811/+[2] http://en.wikipedia.org/wiki/Escape_velocity+[3] http://jscience.org/api/org/jscience/physics/models/package-summary.html+[4] http://www.haskell.org/haskellwiki/Physical_units+[5] http://liftm.wordpress.com/2007/06/03/scientificdimension-type-arithmetic-and-physical-units-in-haskell/+[6] http://jscience.org/+[7] http://research.sun.com/projects/plrg/fortress.pdf+
+ Numeric/Units/Dimensional/CGS.lhs view
@@ -0,0 +1,320 @@+Numeric.Dimensional.CGS -- CGS system of units+Bjorn Buckwalter, bjorn.buckwalter@gmail.com+License: BSD3++*** EXPERIMENTAL ***+++= Introduction =++This module was prompted by an email from Chuck Blake[1]. He asked if+the Dimensional library could support other systems of units than+SI, in particular systems such as the centimeter-gram-second (CGS)+system where fractional exponents of dimensions occur. He also+wondered whether it was possible to convert quantities between+different systems while statically ensuring that a given conversion+was valid.++In this module we show that we can in a straight forward manner+support systems with rational exponents, provided that the rationals+that may be encountered are known a priori. As an example we provide+a rudimentary implementation of the CGS system. ++We also show that we can indeed statically prohibit invalid conversions+between different systems.+++= Caveats =++I'm ignorantly assuming that when working with the CGS (or MKS)+system you will only (meaningfully?) encounter half-exponents and+only of the length and mass dimensions. Of course, in other systems+other rational exponents may be encountered.++I am also assuming that the CGS system would not be employed when+working with temperature, amount or luminosity. This is evident in+the below type signatures where I have assumed zero extent in the+temperature, amount and luminosity dimensions. If this is incorrect+I would appreciate pointers to the CGS representation of these+dimensions.++Please correct and inform me if my assumptions are wrong! +++= Preliminaries =++> {-# LANGUAGE UndecidableInstances, ScopedTypeVariables #-}++> module Numeric.Units.Dimensional.CGS where++> import Prelude ( undefined, Num, Fractional, Floating, Show, recip, Double )+> import qualified Prelude+> import Numeric.Units.Dimensional hiding ( DLength, DMass, DTime, DElectricCurrent )+> import Numeric.Units.Dimensional.Quantities as SIQ+> import qualified Numeric.Units.Dimensional.SIUnits as SI+> import qualified Numeric.NumType as N+> import Numeric.NumType ( Neg2, Neg1, Zero, Pos, Pos1, Pos2, Pos3, NumType )+> import Numeric.NumType ( neg2, neg1, zero, pos1, pos2, pos3 )+> import Data.Maybe (catMaybes)+ ++= Dimensions =++Analogously with the SI we collect the base dimensions of the CGS+system in the data type 'CGSDim'.++> data CGSDim lh mh t++In the above 'lh' and 'mh' represent the number of half-exponents+of length and mass respectively while 't' represents the number of+whole-exponents. The base dimensions illustrate this.++> type DLength = CGSDim Pos2 Zero Zero+> type DMass   = CGSDim Zero Pos2 Zero+> type DTime   = CGSDim Zero Zero Pos1++We add a few non-base dimensions for the sake of example. Charge+is particularly interesting as it illustrates the need for+half-exponents as described in [2].++> type DElectricCurrent = CGSDim Pos3 Pos1 Neg2+> type DCharge = CGSDim Pos3 Pos1 Neg1+++= 'Mul', 'Div', 'Pow' and 'Root' instances =++The 'Mul', 'Div', 'Pow' and 'Root' instances are strictly analogous+with the SI.++> instance ( N.Sum lh lh' lh''+>          , N.Sum mh mh' mh''+>          , N.Sum t  t'  t'' ) => Mul (CGSDim lh   mh   t) +>                                      (CGSDim lh'  mh'  t') +>                                      (CGSDim lh'' mh'' t'')++> instance ( N.Sum lh lh' lh''+>          , N.Sum mh mh' mh''+>          , N.Sum t  t'  t'' ) => Div (CGSDim lh'' mh'' t'') +>                                      (CGSDim lh'  mh'  t') +>                                      (CGSDim lh   mh   t)++> instance ( N.Mul lh x lh'+>          , N.Mul mh x mh'+>          , N.Mul t  x t' ) => Pow (CGSDim lh  mh  t) x +>                                   (CGSDim lh' mh' t')++> instance ( N.Div lh x lh'+>          , N.Div mh x mh'+>          , N.Div t  x t' ) => Root (CGSDim lh  mh  t) x +>                                    (CGSDim lh' mh' t')+++= Units =++We define the base units of the system. By defining 'meter' with a+"scale" of 100 we get a scale of one for 'centi meter'.++> meter  :: Num a => Unit DLength a+> meter  = Dimensional 100+> gram   :: Num a => Unit DMass a+> gram   = Dimensional 1+> second :: Num a => Unit DTime a+> second = Dimensional 1++We continue by defining the CGS equivalents of the other base SI+units. Actually we limit ourselves to 'ampere' since I am not sure+if or how the SI base dimensions other than current are expressed+in CGS.++> ampere :: Floating a => Unit DElectricCurrent a+> ampere = prefix (recip 3.33564e-10) ((SI.centi meter ^ pos3) ^/ pos2 * gram ^/ pos2 * second ^ neg2)++We also define the preferred CGS unit for charge.++> franklin :: Floating a => Unit DCharge a -- Also known as "esu".+> franklin = gram ^/ pos2 * (SI.centi meter ^ pos3) ^/ pos2 / second+++= Conversion from SI =++At some point we may wish to convert an SI quantity to a CGS quantity+or vice versa.++In order to convert a 'Quantity' from the SI system to the CGS+system we use the strategy of dividing the quantity by the SI base+unit and multiplying the resulting number (sans dimension) by the+equivalent CGS unit. To realize this strategy we must be able to+obtain the SI base unit and the equivalent CGS unit for a given+quantity. We start with the SI unit since it is trivial.++> unit_SI :: Num a => Quantity (Dim l m t i th n j) a -> Unit (Dim l m t i th n j) a+> unit_SI _ = Dimensional 1++(Perhaps the above function would be better defined in another+module.)++Obtaining the CGS unit corresponding to the SI base unit of a+Quantity isn't quite as trivial. The function body itself is+straight-forward enough, the hairy part is the type signature.++> unit_CGS :: forall a l m t i l2 m2 il it l' m' t'.+>          ( Floating a+>          , N.Mul Zero l Zero, N.Mul Pos2 l l2+>          , N.Mul Zero m Zero, N.Mul Pos2 m m2+>          , N.Mul Zero t Zero, N.Mul Pos1 t t+>          , N.Sum l2 Zero l2+>          , N.Sum Zero m2 m2,  N.Sum m2 Zero m2+>          , N.Sum Zero t  t+>          , N.Mul Pos3 i  il+>          , N.Mul Pos1 i  i+>          , N.Mul Neg2 i  it+>          , N.Sum l2 il l'+>          , N.Sum m2 i  m'+>          , N.Sum t  it t'+>          ) => Quantity (Dim l m t i Zero Zero Zero) a -> Unit (CGSDim l' m' t') a+> unit_CGS _ = meter        ^ (undefined :: l)+>            * SI.kilo gram ^ (undefined :: m)+>            * second       ^ (undefined :: t)+>            * ampere       ^ (undefined :: i)++Note that since the base dimensions of the CGS are a subset of those+of the SI the mapping of types from SI to CGS is unambiguous.++Also note that complex as the type signature may be producing it is a+mostly mechanical process.++With the above two functions we can define the function that converts+a unit from the SI. We omit the type signature since it is hairy+but can be readily inferred.++> fromSI x = x /~ unit_SI  x *~ unit_CGS x+++= Conversion to SI =++We use the same strategy to convert from CGS to SI. However, when+converting from CGS to SI there may be several valid SI dimensionalities+for any given CGS dimensionality. We will handle this ambiguity by+requiring the user to specify the desired type (except when it is+inferable) of the resulting quantity.  For example:++] toSI (3.2 *~ centi meter) :: Length Double++In order to do this we must employ lexically scoped type variables+and provide the hairy type signature for the 'toSI' function.++> toSI :: forall a l m t i l2 m2 il it l' m' t'.+>          ( Floating a+>          , N.Mul Zero l Zero, N.Mul Pos2 l l2+>          , N.Mul Zero m Zero, N.Mul Pos2 m m2+>          , N.Mul Zero t Zero, N.Mul Pos1 t t+>          , N.Sum l2 Zero l2+>          , N.Sum Zero m2 m2,  N.Sum m2 Zero m2+>          , N.Sum Zero t  t+>          , N.Mul Pos3 i  il+>          , N.Mul Pos1 i  i+>          , N.Mul Neg2 i  it+>          , N.Sum l2 il l'+>          , N.Sum m2 i  m'+>          , N.Sum t  it t'+>          ) => Quantity (CGSDim l' m' t') a -> Quantity (Dim l m t i Zero Zero Zero) a+> toSI x = x /~ unit_CGS (undefined :: Quantity (Dim l m t i Zero Zero Zero) a)+>            *~ unit_SI  (undefined :: Quantity (Dim l m t i Zero Zero Zero) a)++Again, the type signature is complex but deriving it is a mechanical+process.+++= 'Show' instance =++We round off by writing 'Show' instance for 'CGSDim' analogous to+that of 'Dim'.++Out of laziness we use the notation "sqrt(cm)" to represent halves+of integral dimensions. Nothing is technically keeping us from doing+a better job here.++> instance forall lh mh t.+>     ( NumType lh+>     , NumType mh+>     , NumType t+>     ) => Show (CGSDim lh mh t) where+>     show _ = (Prelude.unwords Prelude.. catMaybes)+>              [ dimUnit "sqrt(cm)" (undefined :: lh)+>              , dimUnit "sqrt(g)"  (undefined :: mh)+>              , dimUnit "s"        (undefined :: t)+>              ]+++= Examples =++Let us try the Coulomb attraction example from [2]. We start by+performing the calculation in the SI.++> q_si  = 1.6021773e-19 *~ SI.coulomb -- Elementary charge in SI.+> r_si  = 0.1 *~ SI.nano SI.meter     -- Distance in SI+> f_si  = q_si ^ pos2 / (_4 * pi * e0 * r_si ^ pos2) +>   where +>       e0 = 8.8541878e-12 *~ (SI.ampere * SI.second / (SI.volt * SI.meter)) ++The same calculation in the CGS system.++> q_cgs = fromSI q_si -- Elementary charge in CGS.+> r_cgs = fromSI r_si -- Distance in CGS+> f_cgs = q_cgs ^ pos2 / r_cgs ^ pos2++Inspecting the values in GHCi shows us that the results are consistent+(within reasonable accuracy) with [2].++  *Numeric.Dimensional.CGS> f_si+  2.3070794737101255e-8 m kg s^-2+  *Numeric.Dimensional.CGS> f_cgs +  2.30708078598602e-3 sqrt(cm)^2 sqrt(g)^2 s^-2++To convert from CGS to SI we must specify the type of the SI 'Quantity'.++> f_si' = toSI f_cgs :: SIQ.Force Double++  *Numeric.Dimensional.CGS> f_si'+  2.3070807859860202e-8 m kg s^-2++We follow up with another conversion example demonstrating the+ambiguity in the conversion from CGS to SI.++> c     = 1 *~ SI.farad -- A SI capacitance.+> c_cgs = fromSI c      -- Capacitance has dimensionality L in CGS.+> c'    = toSI c_cgs :: SIQ.Capacitance Double+> c''   = toSI c_cgs :: Length Double++  *Numeric.Dimensional.CGS> c+  1.0 m^-2 kg^-1 s^4 A^2+  *Numeric.Dimensional.CGS> c_cgs+  8.98755691740885e11 sqrt(cm)^2+  *Numeric.Dimensional.CGS> c'+  1.0 m^-2 kg^-1 s^4 A^2+  *Numeric.Dimensional.CGS> c''+  8.98755691740885e9 m+++= Future work =++This is a very rudimentary implementation. To make it more practical+a significant number of quantities and units, in particularly those+commonly used with the CGS, would need to be added. In the mean+time all units defined for the SI can be used with the CGS by+applying 'fromSI' to quantities defined from the SI units.++If anyone is willing to add quantities/units (or other enhancements)+I will happily to accept patches. Personally I do not expect to use+this module and therefore do not intend to invest much more time+in it. If the module has other users I might reconsider.++And of course, another direction of future work is to define+additional systems (e.g. natural, relativistic) using this module+as a template. I imagine this should be fairly straight forward.+++= References =++[1] http://code.google.com/p/dimensional/wiki/ChuckBlake20070611+[2] http://www.tf.uni-kiel.de/matwis/amat/mw1_ge/kap_2/basics/b2_1_14.html
+ Numeric/Units/Dimensional/Extensible.lhs view
@@ -0,0 +1,168 @@+Numeric.Dimensional.Extensible -- Extensible physical dimensions+Bjorn Buckwalter, bjorn.buckwalter@gmail.com+License: BSD3+++= Summary =++On January 3 Mike Gunter asked[1]:++| The very nice Buckwalter and Denney dimensional-numbers packages+| both work on a fixed set of base dimensions.  This is a significant+| restriction for me--I want to avoid adding apples to oranges as+| well as avoiding adding meters to grams.  Is it possible to have+| an extensible set of base dimensions?  If so, how usable can such+| a system be made?  Is it very much worse than a system with a fixed+| set of base dimensions?++In this module we facilitate the addition an arbitrary number of+"extra" dimensions to the seven base dimensions defined in+'Numeric.Dimensional'. A quantity or unit with one or more extra+dimensions will be referred to as an "extended Dimensional".+++= Preliminaries =++Similarly with 'Numeric.Dimensional' this module requires GHC+6.6 or later.++> {-# LANGUAGE UndecidableInstances, ScopedTypeVariables #-}++> module Numeric.Units.Dimensional.Extensible ( DExt, showDExt ) where++> import Numeric.Units.Dimensional ( Dim, Mul, Div, Pow, Root, dimUnit )+> import Numeric.NumType ( NumType, Sum, Negate, Zero, Pos, Neg ) +> import qualified Numeric.NumType as N ( Div, Mul )+++= 'DExt', 'Apples' and 'Oranges' =++We define the datatype 'DExt' which we will use to increase the+number of dimensions from the seven SI base dimensions to an arbitrary+number of dimensions.++> data DExt a n d++The type variable 'a' is used to tag the extended dimensions with+an identity, thus preventing inadvertent mixing of extended dimensions.++Using 'DExt' we can define type synonyms for extended dimensions+applicable to our problem domain. For example, Mike Gunter could+define the 'Apples' and 'Oranges' dimensions and the corresponding+quantities.++] data TApples -- Type tag.+] type DApples  = DExt TApples Pos1 DOne+] type Apples   = Quantity DApples++] data TOrange -- Type tag.+] type DOranges = DExt TApples Zero (DExt TOranges Pos1 DOne)+] type Oranges  = Quantity DOranges++And while he was at it he could define corresponding units.++] apple  :: Num a => Unit DApples a+] apple  = Dimensional 1+] orange :: Num a => Unit DOranges a+] orange = Dimensional 1++When extending dimensions we adopt the convention that the first+(outermost) dimension is the reference for aligning dimensions, as+shown in the above example. This is important when performing+operations on two Dimensionals with a differing number of extended+dimensions.+++= 'Show' helper function =++We provide a helper function to ease defining 'Show' instances.++> showDExt :: forall a n d. (NumType n, Show d) => String -> DExt a n d -> String+> showDExt u _ = showHelp (dimUnit u (undefined :: n)) (show (undefined :: d))+>        where+>            showHelp Nothing  s  = s+>            showHelp (Just s) s' = s ++ " " ++ s'++Using this helper function defining 'Show' instances for the dimensions+with extent in apples and oranges is simple.++] instance (NumType n, Show d) => Show (DExt TApples n d) where+]   show = showDExt "apple" +] instance (NumType n, Show d) => Show (DExt TOranges n d) where+]   show = showDExt "orange" +++= The 'DropZero' class =++The choice of convention may seem backwards considering the opposite+convention is used for NumTypes (though for NumTypes the distinction+is arguably irrelevant). However, this choice facilitates relatively+simple interoperability with base dimensions. In particular it lets+us drop any dimensions with zero extent adjacent to the terminating+'Dim'. To capture this property we define the 'DropZero' class.++> class DropZero d d' | d -> d'++The following 'DropZero' instances say that when an extended dimension+with zero extent is next to a 'Dim' the extended dimension can be+dropped. In all other cases the dimensions are retained as is.++> instance DropZero (DExt a Zero (Dim l m t i th n j)) (Dim l m t i th j j)+> instance DropZero (DExt a Zero (DExt a' n d)) (DExt a Zero (DExt a' n d))+> instance DropZero (DExt a (Pos n) d) (DExt a (Pos n) d)+> instance DropZero (DExt a (Neg n) d) (DExt a (Neg n) d)+++= Classes from 'Numeric.Dimensional' = ++We get negation, addition and subtraction for free with extended+Dimensionals. However, we will need instances of the 'Mul', 'Div',+'Pow' and 'Root' classes for the corresponding operations to work.++Multiplication and division can cause dimensions to be eliminated.+We use the 'DropZero' type class to guarantee that the result of a+multiplication or division has a minimal representation.++When only one of the 'Mul' factors is an extended dimensional there is+no need to minimize.++> instance (Mul d (Dim l m t i th n j) d') +>       => Mul (DExt a x d) (Dim l m t i th n j) (DExt a x d')+> instance (Mul (Dim l m t i th n j) d d') +>       => Mul (Dim l m t i th n j) (DExt a x d) (DExt a x d')++If both of the factors are extended the product must be minimized.++> instance (Sum n n' n'', Mul d d' d'', DropZero (DExt a n'' d'') d''') +>       => Mul (DExt a n d) (DExt a n' d') d'''++Analogously for 'Div'.++> instance (Div d (Dim l m t i th n j) d') +>       => Div (DExt a x d) (Dim l m t i th n j) (DExt a x d')+> instance (Div (Dim l m t i th n j) d d', Negate x x') +>       => Div (Dim l m t i th n j) (DExt a x d) (DExt a x' d')++> instance (Sum n'' n' n, Div d d' d'', DropZero (DExt a n'' d'') d''') +>       => Div (DExt a n d) (DExt a n' d') d'''++The instances for 'Pow' and 'Root' are simpler since they can not+change any previously non-zero to be eliminated.++> instance (N.Mul n x n', Pow d x d')   => Pow  (DExt a n d) x (DExt a n' d')+> instance (N.Div n x n', Root  d x d') => Root (DExt a n d) x (DExt a n' d')+++= Note =++The use of 'DExt' is not particularily modular. Exrended dimensions+must adhere to a strict ordering in order to be compatible in terms+of e.g. multiplication. This makes it difficult to add extra+dimensions without full knowledge of all extra dimension one will+be interacting with.+++= References =++[1] http://www.haskell.org/pipermail/haskell-cafe/2007-January/021069.html+
+ Numeric/Units/Dimensional/NonSI.lhs view
@@ -0,0 +1,75 @@+Numeric.Dimensional.NonSI+Bjorn Buckwalter, bjorn.buckwalter@gmail.com+License: BSD3+++= Summary =++This module defines units that are not part of the SI, with the+exception of those defined in the 'SIUnits' module (units outside+of the SI accepted for use with the SI). ++Any chapters, sections or tables referenced are from [1] unless+otherwise specified.++> module Numeric.Units.Dimensional.NonSI where++> import Numeric.Units.Dimensional.Prelude+> import qualified Prelude+++= Neper, bel, shannon and the like =++The units of section 5.1.2 are purposefully (but not permanently)+omitted. In fact the logarithmic units (see section 8.7) are+problematic and it is not clear how to implement them. Perhaps with+a conversion function similar to for degrees Celsius.+++= Table 7 =++"Units accepted for use with the SI whose values in SI units are+obtained experimentally."++When [1] was published The electronvolt had a standard combined+uncertainity of 0.00000049e-19 J and the unified atomic mass unit+had a combined uncertainty of 0.0000010e-27 kg.++> electronVolt :: Fractional a => Unit DEnergy a+> electronVolt = prefix 1.60217733e-19 joule+> unifiedAtomicMassUnit :: Fractional a => Unit DMass a+> unifiedAtomicMassUnit = prefix 1.6605402e-27 (kilo gram)+++= Other units =++Some US customary (that is, inch-pound) units.++> inch, foot :: Fractional a => Unit DLength a+> inch = prefix 2.54 (centi meter)+> foot = prefix 12 inch     -- 0.3048 m+> poundMass :: Fractional a => Unit DMass a+> poundMass = prefix 0.45359237 (kilo gram)++In order to relate pounds mass to pounds force we define the+questionable unit 'gee' (G) as the gravitational acceleration at+sea level. Note that 'gee' is experimental and has an inherent+uncertainty which also transfers to 'poundForce'.++> gee :: Fractional a => Unit DAcceleration a+> gee = prefix 9.80665 meter / second ^ pos2+> poundForce :: Fractional a => Unit DForce a+> poundForce = poundMass * gee  -- 4.4482 N++Other (non inch-pound) units.++> bar :: (Fractional a) => Unit DPressure a+> bar = prefix 1.0e5 pascal+> revolution :: (Floating a) => Unit DOne a+> revolution = prefix 360 degree+++= References =++[1] http://physics.nist.gov/Pubs/SP811/+
+ Numeric/Units/Dimensional/Prelude.hs view
@@ -0,0 +1,27 @@+module Numeric.Units.Dimensional.Prelude +    ( module Numeric.Units.Dimensional+    , module Numeric.Units.Dimensional.Quantities+    , module Numeric.Units.Dimensional.SIUnits+    , module Numeric.NumType+    , module Prelude+    ) where++import Numeric.Units.Dimensional hiding +    ( Dimensional (Dimensional)+    )++import Numeric.Units.Dimensional.Quantities++import Numeric.Units.Dimensional.SIUnits++import Numeric.NumType +    ( neg5, neg4, neg3, neg2, neg1, zero, pos1, pos2, pos3, pos4, pos5+    ) -- ^Used in exponents.++import Prelude hiding+    ( (+), (-), (*), (/), (^), (**)+    , abs, negate, pi, exp, log, sqrt+    , sin, cos, tan, asin, acos, atan, atan2+    , sinh, cosh, tanh, asinh, acosh, atanh+    , sum+    ) -- ^Hide definitions overridden by 'Numeric.Dimensional'.
+ Numeric/Units/Dimensional/Quantities.lhs view
@@ -0,0 +1,301 @@+Numeric.Dimensional.Quantities+Bjorn Buckwalter, bjorn.buckwalter@gmail.com+License: BSD3+++= Summary =++This module defines type synonyms for common dimensionalities and+the associated quantity types. Additional dimensionalities and+quantity types will be added on an as-needed basis.++The definitions in this module are grouped so that a type synonym+for the dimensionality is defined first in terms of base dimension+exponents. Then a type synonym for the corresponding quantity type+is defined. If there are several quantity types with the same+dimensionality type synonyms are provided for each quantity type.++> module Numeric.Units.Dimensional.Quantities where++> import Numeric.Units.Dimensional +>   ( Dim, Quantity, Dimensionless+>   , DOne, DLuminousIntensity, DThermodynamicTemperature+>   , Unit, DLength, (^+) -- Used only for 'square' and 'cubic'.+>   )+> import Numeric.NumType +>   ( Neg3, Neg2, Neg1, Zero, Pos1, Pos2, Pos3, Pos4+>   , pos2, pos3 -- Used only for 'square' and 'cubic'.+>   )+++= Quantities from [1] =++The following quantities are all from the NIST publication "Guide+for the Use of the International System of Units (SI)" [1]. Any+chapters, sections or tables referenced are from [1] unless otherwise+specified.++For lack of better organization we provide definitions grouped by+table in [1].+++== Table 2 ==++"Examples of SI derived units expressed in terms of SI base units."++> type DArea = Dim Pos2 Zero Zero Zero Zero Zero Zero+> type Area  = Quantity DArea++> type DVolume = Dim Pos3 Zero Zero Zero Zero Zero Zero+> type Volume  = Quantity DVolume++> type DVelocity = Dim Pos1 Zero Neg1 Zero Zero Zero Zero+> type Velocity  = Quantity DVelocity++> type DAcceleration = Dim Pos1 Zero Neg2 Zero Zero Zero Zero+> type Acceleration  = Quantity DAcceleration++> type DWaveNumber = Dim Neg1 Zero Zero Zero Zero Zero Zero+> type WaveNumber  = Quantity DWaveNumber++> type DMassDensity = Dim Neg3 Pos1 Zero Zero Zero Zero Zero+> type MassDensity  = Quantity DMassDensity+> type Density      = MassDensity -- Short name.++> type DSpecificVolume = Dim Pos3 Neg1 Zero Zero Zero Zero Zero +> type SpecificVolume  = Quantity DSpecificVolume++> type DCurrentDensity = Dim Neg2 Zero Zero Pos1 Zero Zero Zero+> type CurrentDensity  = Quantity DCurrentDensity++> type DMagneticFieldStrength = Dim Neg1 Zero Zero Pos1 Zero Zero Zero+> type MagneticFieldStrength  = Quantity DMagneticFieldStrength++> type DAmountOfSubstanceConcentration = Dim Neg3 Zero Zero Zero Zero Pos1 Zero+> type AmountOfSubstanceConcentration  = Quantity DAmountOfSubstanceConcentration+> type Concentration                   = AmountOfSubstanceConcentration -- Short name.++> type DLuminance = Dim Neg2 Zero Zero Zero Zero Zero Pos1+> type Luminance  = Quantity DLuminance++=== Powers of length units ===++It is permissible to express powers of length units by prefixing+'square' and 'cubic' (see section 9.6 "Spelling unit names raised+to powers" of [1]).++> square :: (Num a) => Unit DLength a -> Unit DArea a+> square x = x ^+ pos2+> cubic  :: (Num a) => Unit DLength a -> Unit DVolume a+> cubic  x = x ^+ pos3++These definitions may seem slightly out of place but these is no+obvious place where they should be. Here they are at least close+to the definitions of 'DLength' and 'DVolume'.+++== Table 3a ==++"SI derived units with special names and symbols, including the+radian and steradian."++> type DPlaneAngle = DOne+> type PlaneAngle  = Dimensionless++> type DSolidAngle = DOne+> type SolidAngle  = Dimensionless++> type DFrequency = Dim Zero Zero Neg1 Zero Zero Zero Zero+> type Frequency  = Quantity DFrequency++> type DForce = Dim Pos1 Pos1 Neg2 Zero Zero Zero Zero+> type Force  = Quantity DForce++> type DPressure = Dim Neg1 Pos1 Neg2 Zero Zero Zero Zero+> type DStress   = DPressure+> type Pressure  = Quantity DPressure+> type Stress    = Quantity DStress++> type DEnergy         = Dim Pos2 Pos1 Neg2 Zero Zero Zero Zero+> type DWork           = DEnergy+> type DQuantityOfHeat = DEnergy+> type Energy          = Quantity DEnergy+> type Work            = Quantity DWork+> type QuantityOfHeat  = Quantity DQuantityOfHeat++> type DPower       = Dim Pos2 Pos1 Neg3 Zero Zero Zero Zero+> type DRadiantFlux = DPower+> type Power        = Quantity DPower+> type RadiantFlux  = Quantity DRadiantFlux++> type DElectricCharge        = Dim Zero Zero Pos1 Pos1 Zero Zero Zero+> type DQuantityOfElectricity = DElectricCharge+> type ElectricCharge         = Quantity DElectricCharge+> type QuantityOfElectricity  = Quantity DQuantityOfElectricity++> type DElectricPotential   = Dim Pos2 Pos1 Neg3 Neg1 Zero Zero Zero+> type DPotentialDifference = DElectricPotential+> type DElectromotiveForce  = DElectricPotential+> type ElectricPotential    = Quantity DElectricPotential+> type PotentialDifference  = Quantity DPotentialDifference+> type ElectromotiveForce   = Quantity DElectromotiveForce++> type DCapacitance = Dim Neg2 Neg1 Pos4 Pos2 Zero Zero Zero+> type Capacitance  = Quantity DCapacitance++> type DElectricResistance = Dim Pos2 Pos1 Neg3 Neg2 Zero Zero Zero+> type ElectricResistance  = Quantity DElectricResistance++> type DElectricConductance = Dim Neg2 Neg1 Pos3 Pos2 Zero Zero Zero+> type ElectricConductance  = Quantity DElectricConductance++> type DMagneticFlux = Dim Pos2 Pos1 Neg2 Neg1 Zero Zero Zero+> type MagneticFlux  = Quantity DMagneticFlux++> type DMagneticFluxDensity = Dim Zero Pos1 Neg2 Neg1 Zero Zero Zero+> type MagneticFluxDensity  = Quantity DMagneticFluxDensity++> type DInductance = Dim Pos2 Pos1 Neg2 Neg2 Zero Zero Zero+> type Inductance  = Quantity DInductance++> type DLuminousFlux = DLuminousIntensity+> type LuminousFlux  = Quantity DLuminousFlux++> type DIlluminance = Dim Neg2 Zero Zero Zero Zero Zero Pos1+> type Illuminance  = Quantity DIlluminance++> type DCelsiusTemperature = DThermodynamicTemperature+> type CelsiusTemperature  = Quantity DCelsiusTemperature+++== Table 3b ==++"SI derived units with special names and symbols admitted for reasons+of safeguarding human health"++> type DActivity = DFrequency -- Activity of a radionuclide.+> type Activity  = Quantity DActivity++> type DAbsorbedDose   = Dim Pos2 Zero Neg2 Zero Zero Zero Zero+> type DSpecificEnergy = DAbsorbedDose+> type DKerma          = DAbsorbedDose+> type AbsorbedDose    = Quantity DAbsorbedDose+> type SpecificEnergy  = Quantity DSpecificEnergy -- Specific energy imparted.+> type Kerma           = Quantity DKerma++> type DDoseEquivalent            = DAbsorbedDose+> type DAmbientDoseEquivalent     = DDoseEquivalent+> type DDirectionalDoseEquivalent = DDoseEquivalent+> type DPersonalDoseEquivalent    = DDoseEquivalent+> type DEquivalentDose            = DDoseEquivalent+> type DoseEquivalent             = Quantity DDoseEquivalent+> type AmbientDoseEquivalent      = DoseEquivalent+> type DirectionalDoseEquivalent  = DoseEquivalent+> type PersonalDoseEquivalent     = DoseEquivalent+> type EquivalentDose             = DoseEquivalent+++== Table 4 ==++"Examples of SI derived units expressed with the aid of SI derived+units having special names and symbols."++We use the same grouping as for table 2.++> type DAngularVelocity = DFrequency+> type AngularVelocity  = Quantity DAngularVelocity++> type DAngularAcceleration = Dim Zero Zero Neg2 Zero Zero Zero Zero+> type AngularAcceleration  = Quantity DAngularAcceleration++> type DDynamicViscosity = Dim Neg1 Pos1 Neg1 Zero Zero Zero Zero+> type DynamicViscosity  = Quantity DDynamicViscosity++> type DMomentOfForce = DEnergy+> type MomentOfForce  = Quantity DMomentOfForce++> type DSurfaceTension = Dim Zero Pos1 Neg2 Zero Zero Zero Zero+> type SurfaceTension  = Quantity DSurfaceTension++> type DHeatFluxDensity = Dim Zero Pos1 Neg3 Zero Zero Zero Zero+> type DIrradiance      = DHeatFluxDensity+> type HeatFluxDensity  = Quantity DHeatFluxDensity+> type Irradiance       = Quantity DIrradiance++> type DRadiantIntensity = DPower+> type RadiantIntensity  = Quantity DRadiantIntensity++> type DRadiance = DIrradiance+> type Radiance  = Quantity DRadiance++> type DHeatCapacity = Dim Pos2 Pos1 Neg2 Zero Neg1 Zero Zero+> type DEntropy      = DHeatCapacity+> type HeatCapacity  = Quantity DHeatCapacity+> type Entropy       = Quantity DEntropy++> type DSpecificHeatCapacity = Dim Pos2 Zero Neg2 Zero Neg1 Zero Zero+> type DSpecificEntropy      = DSpecificHeatCapacity+> type SpecificHeatCapacity  = Quantity DSpecificHeatCapacity+> type SpecificEntropy       = Quantity DSpecificEntropy++Specific energy was already defined in table 3b.++> type DThermalConductivity = Dim Pos1 Pos1 Neg3 Zero Neg1 Zero Zero+> type ThermalConductivity  = Quantity DThermalConductivity++> type DEnergyDensity = DPressure+> type EnergyDensity  = Quantity DEnergyDensity++> type DElectricFieldStrength = Dim Pos1 Pos1 Neg3 Neg1 Zero Zero Zero+> type ElectricFieldStrength  = Quantity DElectricFieldStrength++> type DElectricChargeDensity = Dim Neg3 Zero Pos1 Pos1 Zero Zero Zero+> type ElectricChargeDensity  = Quantity DElectricChargeDensity++> type DElectricFluxDensity = Dim Neg2 Zero Pos1 Pos1 Zero Zero Zero+> type ElectricFluxDensity  = Quantity DElectricFluxDensity++> type DPermittivity = Dim Neg3 Neg1 Pos4 Pos2 Zero Zero Zero+> type Permittivity  = Quantity DPermittivity++> type DPermeability = Dim Pos1 Pos1 Neg2 Neg2 Zero Zero Zero+> type Permeability  = Quantity DPermeability++> type DMolarEnergy = Dim Pos2 Pos1 Neg2 Zero Zero Neg1 Zero+> type MolarEnergy  = Quantity DMolarEnergy++> type DMolarEntropy      = Dim Pos2 Pos1 Neg2 Zero Neg1 Neg1 Zero+> type DMolarHeatCapacity = DMolarEntropy+> type MolarEntropy       = Quantity DMolarEntropy+> type MolarHeatCapacity  = Quantity DMolarHeatCapacity++> type DExposure = Dim Zero Neg1 Pos1 Pos1 Zero Zero Zero+> type Exposure  = Quantity DExposure -- Exposure to x and gamma rays.++> type DAbsorbedDoseRate = Dim Pos2 Zero Neg3 Zero Zero Zero Zero+> type AbsorbedDoseRate  = Quantity DAbsorbedDoseRate+++= Quantities not defined in [1] =++Here we define additional quantities on an as-needed basis. We also+provide some synonyms that we anticipate will be useful.++> type DImpulse = Dim Pos1 Pos1 Neg1 Zero Zero Zero Zero+> type Impulse  = Quantity DImpulse++> type DMassFlow = Dim Zero Pos1 Neg1 Zero Zero Zero Zero+> type MassFlow  = Quantity DMassFlow++For these we don't bother defining new type synonyms for dimensionalities.+Is this rational?++> type Angle             = PlaneAngle -- Abbreviation+> type Thrust            = Force+> type EnergyPerUnitMass = SpecificEnergy+++= References =++[1] http://physics.nist.gov/Pubs/SP811/+
+ Numeric/Units/Dimensional/SIUnits.lhs view
@@ -0,0 +1,261 @@+Numeric.Dimensional.SIUnits+Bjorn Buckwalter, bjorn.buckwalter@gmail.com+License: BSD3+++= Summary =++This module defines the SI prefixes, the SI base units and the SI+derived units. It also defines the units outside of the SI that are+accepted for use with the SI. Any chapters, sections or tables+referenced are from [1] unless otherwise specified.++> module Numeric.Units.Dimensional.SIUnits where++> import Numeric.Units.Dimensional+> import Numeric.Units.Dimensional.Quantities+> import Numeric.NumType +>   ( Neg3, Neg2, Neg1, Zero, Pos1, Pos2, Pos3, Pos4+>   , neg3, neg2, neg1, pos1, pos2, pos3+>   )+> import Data.Time.Clock (DiffTime)+> import Prelude ( (.), Num, Real (toRational), Fractional (fromRational), Floating, recip )+> import qualified Prelude+++= SI prefixes (section 4.4) =++Prefixes are used to form decimal multiples and submultiples of SI+Units as described in section 4.4. We will define the SI prefixes+in terms of the 'prefix' function which applies a scale factor to a+unit.++We define all SI prefixes from Table 5. Multiples first.++> deka, deca, hecto, kilo, mega, giga, tera, peta, exa, zetta, yotta +>   :: Num a => Unit d a -> Unit d a+> deka  = prefix 10 -- International English.+> deca  = deka      -- American English.+> hecto = deka . deka+> kilo  = deka . hecto+> mega  = kilo . kilo+> giga  = kilo . mega+> tera  = kilo . giga+> peta  = kilo . tera+> exa   = kilo . peta+> zetta = kilo . exa+> yotta = kilo . zetta++Then the submultiples.+ +> deci, centi, milli, micro, nano, pico, femto, atto, zepto, yocto+>   :: Fractional a => Unit d a -> Unit d a+> deci  = prefix 0.1+> centi = deci . deci+> milli = deci . centi+> micro = milli . milli+> nano  = milli . micro+> pico  = milli . nano+> femto = milli . pico+> atto  = milli . femto+> zepto = milli . atto+> yocto = milli . zepto++By defining SI prefixes as functions applied to a 'Unit' we satisfy+section 6.2.6 "Unacceptability of stand-alone prefixes".+++= SI base units (section 4.1) =++Now we will define the SI base unitsi from section 4.1. To avoid a+myriad of one-letter functions that would doubtlessly cause clashes+and frustration in users' code we spell out all unit names in full,+as we did for prefixes. We also elect to spell the unit names in+singular form, as allowed by section 9.7 "Other spelling conventions".++We define the SI base units in the order of table 1.++> metre, meter :: Num a => Unit DLength a+> metre = Dimensional 1 -- International English.+> meter = metre         -- American English.++For mass the SI base unit is kilogram. For sensible prefixes we+define gram here (see section 6.2.7 "Prefixes and the kilogram").+The drawback is that we are forced to use 'Fractional'.++> gram    :: Fractional a => Unit DMass a+> gram    = Dimensional 1e-3+> second  :: Num a => Unit DTime a+> second  = Dimensional 1+> ampere  :: Num a => Unit DElectricCurrent a+> ampere  = Dimensional 1+> kelvin  :: Num a => Unit DThermodynamicTemperature a+> kelvin  = Dimensional 1+> mole    :: Num a => Unit DAmountOfSubstance a+> mole    = Dimensional 1+> candela :: Num a => Unit DLuminousIntensity a+> candela = Dimensional 1+++= DiffTime conversion =++It is not within the scope of this library to handle the complex+task of date and time arithmetic. It is recommended to use the+'Data.Time' library for handling dates and using 'Time' quantities+only when time differences are involved in calculations with other+quantities. In order to convert between the 'DiffTime' data type+in the 'Data.Time' library and 'Time' quantities we provide the+functions 'fromDiffTime' and 'toDiffTime'.++> fromDiffTime :: (Fractional a) => DiffTime -> Time a+> fromDiffTime = (*~ second) . fromRational . toRational+> toDiffTime :: (Real a, Fractional a) => Time a -> DiffTime+> toDiffTime = fromRational . toRational . (/~ second)+++= SI derived units (section 4.2) =++Before defining the derived units themselves we provide type synonyms+for derived quantities and their dimensionalities. For lack of better+organization we provide definitions grouped by table in [1].+++== Table 3a ==++"SI derived units with special names and symbols, including the+radian and steradian."++> radian :: Fractional a => Unit DPlaneAngle a+> radian = one -- meter * meter ^ neg1+> steradian :: Fractional a => Unit DSolidAngle a+> steradian = one -- meter ^ pos2 * meter ^ neg2+> hertz :: Fractional a => Unit DFrequency a+> hertz = second ^ neg1+> newton :: Fractional a => Unit DForce a+> newton = kilo gram * meter * second ^ neg2+> pascal :: Fractional a => Unit DPressure a+> pascal = newton / meter ^ pos2+> joule :: Fractional a => Unit DEnergy a+> joule = newton * meter+> watt :: Fractional a => Unit DPower a+> watt = joule / second+> coulomb :: Fractional a => Unit DElectricCharge a+> coulomb = second * ampere+> volt :: Fractional a => Unit DElectricPotential a+> volt = watt / ampere+> farad :: Fractional a => Unit DCapacitance a+> farad = coulomb / volt+> ohm :: Fractional a => Unit DElectricResistance a+> ohm = volt / ampere+> siemens :: Fractional a => Unit DElectricConductance a+> siemens = ampere / volt+> weber :: Fractional a => Unit DMagneticFlux a+> weber = volt * second+> tesla :: Fractional a => Unit DMagneticFluxDensity a+> tesla = weber / meter ^ pos2+> henry :: Fractional a => Unit DInductance a+> henry = weber / ampere++We defer the definition of Celcius temperature to the end (would+appear here if we stricly followed table 3a).++> lumen :: Fractional a => Unit DLuminousFlux a+> lumen = candela / steradian+> lux :: Fractional a => Unit DIlluminance a+> lux = lumen / meter ^ pos2++=== Degree Celsius ===++A problematic area is units which increase proportionally to the+base SI units but cross zero at a different point. An example would+be degrees Celsius (see section 4.2.1.1). The author feels that it+is appropriate to define a unit for use with relative quantities+(taking only into account the proportionality) and complement the+unit with functions for converting absolute values.++> degreeCelsius :: Num a => Unit DCelsiusTemperature a+> degreeCelsius = kelvin++The function 'fromDegreeCelsiusAbsolute' should be used in lieu of+"*~ degreeCelsius" when working with absolute temperatures. Similarily,+'toDegreeCelsiusAbsolute' should be used in lieu of "/~ degreeCelsius"+when working with absolute temperatures.++> fromDegreeCelsiusAbsolute :: Fractional a => a -> ThermodynamicTemperature a+> fromDegreeCelsiusAbsolute x = x *~ degreeCelsius + 273.15 *~ degreeCelsius+> toDegreeCelsiusAbsolute :: Fractional a => ThermodynamicTemperature a -> a+> toDegreeCelsiusAbsolute x = (x - 273.15 *~ degreeCelsius) /~ degreeCelsius+++== Table 3b ==++"SI derived units with special names and symbols admitted for reasons+of safeguarding human health"++We use the same grouping as for table 3a.++> becquerel :: Fractional a => Unit DActivity a+> becquerel = second ^ neg1++Above we gave a new name to the dimensionality instead of reusing+'Frequency' in the quantity type definition. This will allow GHCi+be more specific when queried for the type of 'becquerel'. For+quantity types without a specific unit we don't bother doing this+(though perhaps we should in case there is a non-SI unit for the+quantity type?).++> gray :: Fractional a => Unit DAbsorbedDose a+> gray = joule / kilo gram+> sievert :: Fractional a => Unit DDoseEquivalent a+> sievert = joule / kilo gram+++= Units outside the SI =++There are several units that are not strictly part of the SI but+are either permanently or temporarily accepted for use with the SI.+We define the permanently accepted ones in this module.++== Table 6 ==++"Units accepted for use with the SI."++We start with time which we grant exclusive rights to 'minute' and+'second'.++> minute, hour, day :: Num a => Unit DTime a+> minute = prefix 60 second+> hour   = prefix 60 minute+> day    = prefix 24 hour -- Mean solar day.++Since 'minute' and 'second' are already in use for time we use+'arcminute' and 'arcsecond' [2] for plane angle instead.++> degree, arcminute, arcsecond :: Floating a => Unit DPlaneAngle a+> degree = prefix (Prelude.pi Prelude./ 180) radian+> arcminute = prefix (recip 60) degreeOfArc+> arcsecond = prefix (recip 60) minuteOfArc++Alternate (longer) forms of the above. In particular 'degreeOfArc'+can be used if there is a percieved need to disambiguate from e.g.+temperature.++> degreeOfArc, minuteOfArc, secondOfArc :: Floating a => Unit DPlaneAngle a+> degreeOfArc = degree+> secondOfArc = arcsecond+> minuteOfArc = arcminute++> litre, liter :: Fractional a => Unit DVolume a+> litre = deci meter ^ pos3 -- International English.+> liter = litre             -- American English.++> tonne, metricTon :: Fractional a => Unit DMass a+> tonne     = prefix 1000 (kilo gram) -- Name in original SI text.+> metricTon = tonne                   -- American name.+++= References =++[1] http://physics.nist.gov/Pubs/SP811/+[2] http://en.wikipedia.org/wiki/Minute_of_arc+
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ dimensional.cabal view
@@ -0,0 +1,32 @@+Name:                dimensional+Version:             0.7+License:             BSD3+License-File:        LICENSE+Copyright:           Bjorn Buckwalter 2006-2007+Author:              Bjorn Buckwalter +Maintainer:          bjorn.buckwalter@gmail.com+Stability:           mostly stable+Homepage:            http://dimensional.googlecode.com/+Synopsis:            Statically checked physical dimensions.+Description:+    Dimensional is a library providing data types for performing arithmetic+    with physical quantities and units. Information about the physical+    dimensions of the quantities and units is embedded in their types and the+    validity of operations is verified by the type checker at compile time.+    The boxing and unboxing of numerical values as quantities is done by+    multiplication and division with units. The library is designed to, as+    far as is practical, enforce/encourage best practices of unit usage.+    Requires GHC 6.6.1 or later.+Category:            Math+Build-Depends:       base,+                     time+Exposed-Modules:     Numeric.NumType, +                     Numeric.Units.Dimensional, +                     Numeric.Units.Dimensional.Prelude,+                     Numeric.Units.Dimensional.Quantities,+                     Numeric.Units.Dimensional.SIUnits,+                     Numeric.Units.Dimensional.NonSI,+                     Numeric.Units.Dimensional.Extensible,+                     Numeric.Units.Dimensional.CGS+ghc-options:         -O+