module Numeric.Units.Dimensional.UnitNames.Internal
where
import Control.Monad (join)
import Data.Data
#if MIN_VERSION_base(4, 8, 0)
import Data.Foldable (toList)
#else
import Data.Foldable (Foldable, toList)
#endif
import GHC.Generics
import Numeric.Units.Dimensional.Dimensions.TermLevel (Dimension', asList, HasDimension(..))
import Numeric.Units.Dimensional.UnitNames.InterchangeNames
import Numeric.Units.Dimensional.Variants (Metricality(..))
import Prelude hiding ((*), (/), (^), product)
import qualified Prelude as P
data UnitName (m :: Metricality) where
One :: UnitName 'NonMetric
MetricAtomic :: NameAtom ('UnitAtom 'Metric) -> UnitName 'Metric
Atomic :: NameAtom ('UnitAtom 'NonMetric) -> UnitName 'NonMetric
Prefixed :: PrefixName -> UnitName 'Metric -> UnitName 'NonMetric
Product :: UnitName 'NonMetric -> UnitName 'NonMetric -> UnitName 'NonMetric
Quotient :: UnitName 'NonMetric -> UnitName 'NonMetric -> UnitName 'NonMetric
Power :: UnitName 'NonMetric -> Int -> UnitName 'NonMetric
Grouped :: UnitName 'NonMetric -> UnitName 'NonMetric
Weaken :: UnitName 'Metric -> UnitName 'NonMetric
deriving (Typeable)
deriving instance Eq (UnitName m)
instance Show (UnitName m) where
show One = "1"
show (MetricAtomic a) = abbreviation_en a
show (Atomic a) = abbreviation_en a
show (Prefixed a n) = abbreviation_en a ++ show n
show (Product n1 n2) = show n1 ++ " " ++ show n2
show (Quotient n1 n2) = show n1 ++ " / " ++ show n2
show (Power x n) = show x ++ "^" ++ show n
show (Grouped n) = "(" ++ show n ++ ")"
show (Weaken n) = show n
isAtomic :: UnitName m -> Bool
isAtomic (One) = True
isAtomic (MetricAtomic _) = True
isAtomic (Atomic _) = True
isAtomic (Prefixed _ _) = True
isAtomic (Grouped _) = True
isAtomic (Weaken n) = isAtomic n
isAtomic _ = False
isAtomicOrProduct :: UnitName m -> Bool
isAtomicOrProduct (Product _ _) = True
isAtomicOrProduct n = isAtomic n
reduce :: UnitName m -> UnitName m
reduce (One) = One
reduce n@(MetricAtomic _) = n
reduce n@(Atomic _) = n
reduce n@(Prefixed _ _) = n
reduce (Product n1 n2) = reduce' (reduce n1 * reduce n2)
reduce (Quotient n1 n2) = reduce' (reduce n1 * reduce n2)
reduce (Power n x) = reduce' ((reduce n) ^ x)
reduce (Grouped n) = reduce' (Grouped (reduce n))
reduce (Weaken n) = reduce' (Weaken (reduce n))
reduce' :: UnitName m -> UnitName m
reduce' (Product One n) = reduce' n
reduce' (Product n One) = reduce' n
reduce' (Power (Power n x1) x2) = reduce (n ^ (x1 P.* x2))
reduce' (Power (Grouped (Power n x1)) x2) = reduce (n ^ (x1 P.* x2))
reduce' (Power _ 0) = One
reduce' (Power n 1) = reduce' n
reduce' (Grouped n) = reduce' n
reduce' n@(Weaken (MetricAtomic _)) = n
reduce' n = n
data NameAtomType = UnitAtom Metricality
| PrefixAtom
deriving (Eq, Ord, Data, Typeable, Generic)
type PrefixName = NameAtom 'PrefixAtom
nOne :: UnitName 'NonMetric
nOne = One
nMeter :: UnitName 'Metric
nMeter = ucumMetric "m" "m" "metre"
nGram :: UnitName 'Metric
nGram = ucumMetric "g" "g" "gram"
nKilogram :: UnitName 'NonMetric
nKilogram = applyPrefix kilo nGram
nSecond :: UnitName 'Metric
nSecond = ucumMetric "s" "s" "second"
nAmpere :: UnitName 'Metric
nAmpere = ucumMetric "A" "A" "Ampere"
nKelvin :: UnitName 'Metric
nKelvin = ucumMetric "K" "K" "Kelvin"
nMole :: UnitName 'Metric
nMole = ucumMetric "mol" "mol" "mole"
nCandela :: UnitName 'Metric
nCandela = ucumMetric "cd" "cd" "candela"
baseUnitName :: Dimension' -> UnitName 'NonMetric
baseUnitName d = let powers = asList $ dimension d
in reduce . product $ zipWith (^) baseUnitNames powers
baseUnitNames :: [UnitName 'NonMetric]
baseUnitNames = [weaken nMeter, nKilogram, weaken nSecond, weaken nAmpere, weaken nKelvin, weaken nMole, weaken nCandela]
deka, hecto, kilo, mega, giga, tera, peta, exa, zetta, yotta :: PrefixName
deka = prefix "da" "da" "deka"
hecto = prefix "h" "h" "hecto"
kilo = prefix "k" "k" "kilo"
mega = prefix "M" "M" "mega"
giga = prefix "G" "G" "giga"
tera = prefix "T" "T" "tera"
peta = prefix "P" "P" "peta"
exa = prefix "E" "E" "exa"
zetta = prefix "Z" "Z" "zetta"
yotta = prefix "Y" "Y" "yotta"
deci, centi, milli, micro, nano, pico, femto, atto, zepto, yocto :: PrefixName
deci = prefix "d" "d" "deci"
centi = prefix "c" "c" "centi"
milli = prefix "m" "m" "milli"
micro = prefix "u" "μ" "micro"
nano = prefix "n" "n" "nano"
pico = prefix "p" "p" "pico"
femto = prefix "f" "f" "femto"
atto = prefix "a" "a" "atto"
zepto = prefix "z" "z" "zepto"
yocto = prefix "y" "y" "yocto"
applyPrefix :: PrefixName -> UnitName 'Metric -> UnitName 'NonMetric
applyPrefix = Prefixed
infixr 8 ^
infixl 7 *, /
(*) :: UnitName m1 -> UnitName m2 -> UnitName 'NonMetric
a * b = Product (weaken a) (weaken b)
(/) :: UnitName m1 -> UnitName m2 -> UnitName 'NonMetric
n1 / n2 | isAtomicOrProduct n1 = Quotient (weaken n1) (weaken n2)
| otherwise = Quotient (grouped n1) (weaken n2)
(^) :: UnitName m -> Int -> UnitName 'NonMetric
x ^ n | isAtomic x = Power (weaken x) n
| otherwise = Power (grouped x) n
weaken :: UnitName m -> UnitName 'NonMetric
weaken n@(MetricAtomic _) = Weaken n
weaken n@One = n
weaken n@(Atomic _) = n
weaken n@(Prefixed _ _) = n
weaken n@(Product _ _) = n
weaken n@(Quotient _ _) = n
weaken n@(Power _ _) = n
weaken n@(Grouped _) = n
weaken n@(Weaken _) = n
strengthen :: UnitName m -> Maybe (UnitName 'Metric)
strengthen n@(MetricAtomic _) = Just n
strengthen (Weaken n) = strengthen n
strengthen _ = Nothing
relax :: forall m1 m2.(Typeable m1, Typeable m2) => UnitName m1 -> Maybe (UnitName m2)
relax n = go (typeRep (Proxy :: Proxy m1)) (typeRep (Proxy :: Proxy m2)) n
where
metric = typeRep (Proxy :: Proxy 'Metric)
nonMetric = typeRep (Proxy :: Proxy 'NonMetric)
go :: TypeRep -> TypeRep -> UnitName m1 -> Maybe (UnitName m2)
go p1 p2 | p1 == p2 = cast
| (p1 == nonMetric) && (p2 == metric) = join . fmap gcast . strengthen
| (p1 == metric) && (p2 == nonMetric) = cast . weaken
| otherwise = error "Should be unreachable. TypeRep of an unexpected Metricality encountered."
grouped :: UnitName m -> UnitName 'NonMetric
grouped = Grouped . weaken
data NameAtom (m :: NameAtomType)
= NameAtom
{
_interchangeName :: InterchangeName,
abbreviation_en :: String,
name_en :: String
}
deriving (Eq, Ord, Data, Typeable, Generic)
instance HasInterchangeName (NameAtom m) where
interchangeName = _interchangeName
instance HasInterchangeName (UnitName m) where
interchangeName One = InterchangeName { name = "1", authority = UCUM }
interchangeName (MetricAtomic a) = interchangeName a
interchangeName (Atomic a) = interchangeName a
interchangeName (Prefixed p n) = let n' = (name . interchangeName $ p) ++ (name . interchangeName $ n)
a' = max (authority . interchangeName $ p) (authority . interchangeName $ n)
in InterchangeName { name = n', authority = a' }
interchangeName (Product n1 n2) = let n' = (name . interchangeName $ n1) ++ "." ++ (name . interchangeName $ n2)
a' = max (authority . interchangeName $ n1) (authority . interchangeName $ n2)
in InterchangeName { name = n', authority = a' }
interchangeName (Quotient n1 n2) = let n' = (name . interchangeName $ n1) ++ "/" ++ (name . interchangeName $ n2)
a' = max (authority . interchangeName $ n1) (authority . interchangeName $ n2)
in InterchangeName { name = n', authority = a' }
interchangeName (Power n x) = let n' = (name . interchangeName $ n) ++ (show x)
in InterchangeName { name = n', authority = authority . interchangeName $ n }
interchangeName (Grouped n) = let n' = "(" ++ (name . interchangeName $ n) ++ ")"
in InterchangeName { name = n', authority = authority . interchangeName $ n }
interchangeName (Weaken n) = interchangeName n
prefix :: String -> String -> String -> PrefixName
prefix i a f = NameAtom (InterchangeName i UCUM) a f
ucumMetric :: String -> String -> String -> UnitName 'Metric
ucumMetric i a f = MetricAtomic $ NameAtom (InterchangeName i UCUM) a f
ucum :: String -> String -> String -> UnitName 'NonMetric
ucum i a f = Atomic $ NameAtom (InterchangeName i UCUM) a f
dimensionalAtom :: String -> String -> String -> UnitName 'NonMetric
dimensionalAtom i a f = Atomic $ NameAtom (InterchangeName i DimensionalLibrary) a f
atom :: String
-> String
-> String
-> UnitName 'NonMetric
atom i a f = Atomic $ NameAtom (InterchangeName i Custom) a f
type UnitNameTransformer = (forall m.UnitName m -> UnitName 'NonMetric)
type UnitNameTransformer2 = (forall m1 m2.UnitName m1 -> UnitName m2 -> UnitName 'NonMetric)
product :: Foldable f => f (UnitName 'NonMetric) -> UnitName 'NonMetric
product = go . toList
where
go :: [UnitName 'NonMetric] -> UnitName 'NonMetric
go [] = nOne
go [n] = n
go (n : ns) = n * go ns