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
    >            , EmptyDataDecls
    >            , MultiParamTypeClasses
    >            , FunctionalDependencies
    >            , FlexibleInstances
    > #-}

    > {- |
    >    Copyright  : Copyright (C) 2006-2013 Bjorn Buckwalter
    >    License    : BSD3
    >
    >    Maintainer : bjorn.buckwalter@gmail.com
    >    Stability  : Experimental
    >    Portability: GHC only?
    >
    > Please refer to the literate Haskell code for documentation of both API
    > and implementation.
    > -}

    > 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 u') "" = u'
    >            showHelp (Just u') s  = u' ++ " " ++ 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