units-1.0.0: src/Data/Dimensions/DimSpec.hs
{- Data/Dimensions/DimSpec.hs
The units Package
Copyright (c) 2013 Richard Eisenberg
eir@cis.upenn.edu
This file defines the DimSpec kind and operations over lists of DimSpecs
-}
{-# LANGUAGE TypeFamilies, DataKinds, TypeOperators, UndecidableInstances #-}
module Data.Dimensions.DimSpec where
import GHC.Exts (Constraint)
import Data.Dimensions.TypePrelude
import Data.Dimensions.Z
-- | This will only be used at the kind level.
-- It either holds a dimension with its exponent, or the special constant DAny,
-- which can be any combination of dimensions at any exponents. It is used to
-- represent multiplying by 0 somewhere.
data DimSpec star = D star Z | DAny
----------------------------------------------------------
--- Set-like operations ----------------------------------
----------------------------------------------------------
{-
These functions are templates for type-level functions.
remove :: String -> [String] -> [String]
remove _ [] = []
remove s (h:t) = if s == h then t else h : remove s t
member :: String -> [String] -> Bool
member _ [] = False
member s (h:t) = s == h || member s t
extract :: String -> [String] -> ([String], Maybe String)
extract _ [] = ([], Nothing)
extract s (h:t) =
if s == h
then (t, Just s)
else let (resList, resVal) = extract s t in (h : resList, resVal)
reorder :: [String] -> [String] -> [String]
reorder x [] = x
reorder x (h:t) =
case extract h x of
(lst, Nothing) -> reorder lst t
(lst, Just elt) -> elt : (reorder lst t)
-}
infix 4 $=
-- | Do these DimSpecs represent the same dimension?
type family (a :: DimSpec *) $= (b :: DimSpec *) :: Bool where
(D n1 z1) $= (D n2 z2) = n1 :=: n2
DAny $= DAny = True
a $= b = False
-- | @(Extract s lst)@ pulls the DimSpec that matches s out of lst, returning a
-- diminished list and, possibly, the extracted DimSpec.
--
-- @
-- Extract A [A, B, C] ==> ([B, C], Just A
-- Extract D [A, B, C] ==> ([A, B, C], Nothing)
-- @
type family Extract (s :: DimSpec *)
(lst :: [DimSpec *])
:: ([DimSpec *], Maybe (DimSpec *)) where
Extract s '[] = '( '[], Nothing )
Extract s (h ': t) =
If (s $= h)
'(t, Just h)
'(h ': Fst (Extract s t), Snd (Extract s t))
-- kind DimAnnotation = [DimSpec *]
-- a list of DimSpecs forms a full annotation of a quantity's dimension
-- | Reorders a to be the in the same order as b, putting entries not in b at the end
--
-- @
-- Reorder [A 1, B 2] [B 5, A 2] ==> [B 2, A 1]
-- Reorder [A 1, B 2, C 3] [C 2, A 8] ==> [C 3, A 1, B 2]
-- Reorder [A 1, B 2] [B 4, C 1, A 9] ==> [B 2, A 1]
-- Reorder x x ==> x
-- Reorder x [] ==> x
-- Reorder [] x ==> []
-- @
type family Reorder (a :: [DimSpec *]) (b :: [DimSpec *]) :: [DimSpec *] where
Reorder x '[] = x
Reorder x (h ': t) = Reorder' (Extract h x) t
-- | Helper function in 'Reorder'
type family Reorder' (scrut :: ([DimSpec *], Maybe (DimSpec *)))
(t :: [DimSpec *])
:: [DimSpec *] where
Reorder' '(lst, Nothing) t = Reorder lst t
Reorder' '(lst, Just elt) t = elt ': (Reorder lst t)
-- | Check if a @[DimSpec *]@ has a 'DAny' inside it
type family HasAny (lst :: [DimSpec *]) :: Bool where
HasAny '[] = False
HasAny (DAny ': t) = True
HasAny (h ': t) = HasAny t
infix 4 @~
-- | Check if two @[DimSpec *]@s should be considered to be equal
type family (a :: [DimSpec *]) @~ (b :: [DimSpec *]) :: Constraint where
a @~ b = If (HasAny a :||: HasAny b)
(() :: Constraint)
(Normalize (Reorder a b) ~ Normalize b)
----------------------------------------------------------
--- Normalization ----------------------------------------
----------------------------------------------------------
-- | Take a @[DimSpec *]@ and remove any @DimSpec@s with an exponent of 0
type family Normalize' (d :: [DimSpec *]) :: [DimSpec *] where
Normalize' '[] = '[]
Normalize' ((D n Zero) ': t) = Normalize' t
Normalize' (h ': t) = h ': Normalize' t
-- | If a @[DimSpec *]@ has a 'DAny', collapse the whole list to one 'DAny'.
-- Otherwise, normalize the list by removing exponents of 0.
type family Normalize (d :: [DimSpec *]) :: [DimSpec *] where
Normalize d = If (HasAny d) '[DAny] (Normalize' d)
-- | Given two @[DimSpec *]@s, return the one that lacks a 'DAny', if there is one.
type family ChooseFrom (d1 :: [DimSpec *]) (d2 :: [DimSpec *]) :: [DimSpec *] where
ChooseFrom d d = Normalize d
ChooseFrom '[DAny] d2 = Normalize d2 -- common cases
ChooseFrom d1 '[DAny] = Normalize d1
ChooseFrom d1 d2 = Normalize (If (HasAny d1) d2 d1)
----------------------------------------------------------
--- Arithmetic -------------------------------------------
----------------------------------------------------------
infixl 6 @@+
-- | Adds corresponding exponents in two dimension, assuming the lists are
-- ordered similarly.
type family (a :: [DimSpec *]) @@+ (b :: [DimSpec *]) :: [DimSpec *] where
'[] @@+ b = b
a @@+ '[] = a
(DAny ': t1) @@+ b = '[DAny]
a @@+ (DAny ': t2) = '[DAny]
((D name z1) ': t1) @@+ ((D name z2) ': t2) = (D name (z1 #+ z2)) ': (t1 @@+ t2)
a @@+ (h ': t) = h ': (a @@+ t)
infixl 6 @+
-- | Adds corresponding exponents in two dimension
type family (a :: [DimSpec *]) @+ (b :: [DimSpec *]) :: [DimSpec *] where
a @+ b = (Reorder a b) @@+ b
infixl 6 @@-
-- | Subtract exponents in two dimensions, assuming the lists are ordered
-- similarly.
type family (a :: [DimSpec *]) @@- (b :: [DimSpec *]) :: [DimSpec *] where
'[] @@- b = NegList b
a @@- '[] = a
(DAny ': t1) @@- b = '[DAny]
a @@- (DAny ': t2) = '[DAny]
((D name z1) ': t1) @@- ((D name z2) ': t2) = (D name (z1 #- z2)) ': (t1 @@- t2)
a @@- (h ': t) = (NegDim h) ': (a @@- t)
infixl 6 @-
-- | Subtract exponents in two dimensions
type family (a :: [DimSpec *]) @- (b :: [DimSpec *]) :: [DimSpec *] where
a @- b = (Reorder a b) @@- b
-- | negate a single @DimSpec@
type family NegDim (a :: DimSpec *) :: DimSpec * where
NegDim (D n z) = D n (NegZ z)
NegDim DAny = DAny
-- | negate a list of @DimSpec@s
type family NegList (a :: [DimSpec *]) :: [DimSpec *] where
NegList '[] = '[]
NegList (h ': t) = (NegDim h ': (NegList t))
infixl 7 @*
-- | Multiplication of the exponents in a dimension by a scalar
type family (base :: [DimSpec *]) @* (power :: Z) :: [DimSpec *] where
'[] @* power = '[]
((D name num) ': t) @* power = (D name (num #* power)) ': (t @* power)
(DAny ': t) @* power = DAny ': (t @* power)
infixl 7 @/
-- | Division of the exponents in a dimension by a scalar
type family (dims :: [DimSpec *]) @/ (z :: Z) :: [DimSpec *] where
'[] @/ z = '[]
((D name num) ': t) @/ z = (D name (num #/ z)) ': (t @/ z)
(DAny ': t) @/ z = DAny ': (t @/ z)