Skip to content

Commit 70c8689

Browse files
committed
Addressed issue with exponents in UCUM names. Fixes bjornbm#109.
1 parent 72b940b commit 70c8689

File tree

2 files changed

+59
-34
lines changed

2 files changed

+59
-34
lines changed

src/Numeric/Units/Dimensional/UnitNames/Internal.hs

+49-33
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
{-# LANGUAGE FlexibleInstances #-}
1010
{-# LANGUAGE GADTs #-}
1111
{-# LANGUAGE KindSignatures #-}
12+
{-# LANGUAGE LambdaCase #-}
1213
{-# LANGUAGE RankNTypes #-}
1314
{-# LANGUAGE ScopedTypeVariables #-}
1415
{-# LANGUAGE StandaloneDeriving #-}
@@ -172,15 +173,14 @@ asSimple n | isSimple n = weaken n
172173
| otherwise = grouped n
173174

174175
evaluate :: (Group a) => UnitName' m a -> a
175-
evaluate One = mempty
176-
evaluate (MetricAtomic a) = a
177-
evaluate (Atomic a) = a
178-
evaluate (Prefixed p a) = p `mappend` a
179-
evaluate (Product n1 n2) = evaluate n1 `mappend` evaluate n2
180-
evaluate (Quotient n1 n2) = evaluate n1 `mappend` (invert $ evaluate n2)
181-
evaluate (Power n x) = pow (evaluate n) x
182-
evaluate (Grouped n) = evaluate n
183-
evaluate (Weaken n) = evaluate n
176+
evaluate = foldName $ UnitNameFold {
177+
foldOne = mempty
178+
, foldPrefix = mappend
179+
, foldProduct = mappend
180+
, foldQuotient = \n1 n2 -> n1 `mappend` (invert n2)
181+
, foldPower = pow
182+
, foldGrouped = id
183+
}
184184

185185
evaluateMolecules :: (Group b) => (NameMolecule a -> b) -> UnitName' m a -> b
186186
evaluateMolecules _ One = mempty
@@ -229,13 +229,34 @@ eliminateRedundantPowers :: UnitName' m a -> UnitName' m a
229229
eliminateRedundantPowers (Product n1 n2) = Product (eliminateRedundantPowers n1) (eliminateRedundantPowers n2)
230230
eliminateRedundantPowers (Quotient n1 n2) = Quotient (eliminateRedundantPowers n1) (eliminateRedundantPowers n2)
231231
eliminateRedundantPowers (Power One _) = One
232+
eliminateRedundantPowers (Power (Power n x1) x2) = eliminateRedundantPowers (Power n (x1 P.* x2))
232233
eliminateRedundantPowers n@(Power n' x) | x == 0 = One
233234
| x == 1 = eliminateRedundantPowers n'
234235
| otherwise = n
235236
eliminateRedundantPowers (Grouped n) = Grouped (eliminateRedundantPowers n)
236237
eliminateRedundantPowers (Weaken n) = Weaken (eliminateRedundantPowers n)
237238
eliminateRedundantPowers n = n
238239

240+
distributePowers :: UnitName' m a -> UnitName' m a
241+
distributePowers = \case
242+
(Product n1 n2) -> Product (distributePowers n1) (distributePowers n2)
243+
(Quotient n1 n2) -> Quotient (distributePowers n1) (distributePowers n2)
244+
n@(Power _ _) -> go 1 n
245+
(Grouped n) -> Grouped (distributePowers n)
246+
(Weaken n) -> Weaken (distributePowers n)
247+
One -> One
248+
n@(Atomic _) -> n
249+
n@(MetricAtomic _) -> n
250+
n@(Prefixed _ _) -> n
251+
where
252+
go :: Int -> UnitName' m a -> UnitName' 'NonMetric a
253+
go x (Product n1 n2) = Product (go x n1) (go x n2)
254+
go x (Quotient n1 n2) = Quotient (go x n1) (go x n2)
255+
go x (Power n x') = go (x P.* x') n
256+
go x (Grouped n) = Grouped (go x n)
257+
go x (Weaken n) = weaken (go x n)
258+
go x n = Power (weaken n) x
259+
239260
ensureSimpleDenominatorsAndPowers :: UnitName' m a -> UnitName' m a
240261
ensureSimpleDenominatorsAndPowers (Product n1 n2) = Product (ensureSimpleDenominatorsAndPowers n1) (ensureSimpleDenominatorsAndPowers n2)
241262
ensureSimpleDenominatorsAndPowers (Quotient n1 n2) = Quotient (ensureSimpleDenominatorsAndPowers n1) (asSimple $ ensureSimpleDenominatorsAndPowers n2)
@@ -476,31 +497,26 @@ grouped :: UnitName' m a -> UnitName' 'NonMetric a
476497
grouped = Grouped . weaken
477498

478499
ucumName :: (HasUnitName a) => a -> Maybe String
479-
ucumName = ucumName' . unitName
500+
ucumName = foldName f . fmap (nameComponent ucum) . ensureSimpleDenominatorsAndPowers . distributePowers . unitName
480501
where
481-
ucumName' :: UnitName m -> Maybe String
482-
ucumName' One = Just "1"
483-
ucumName' (MetricAtomic a) = nameComponent ucum a
484-
ucumName' (Atomic a) = nameComponent ucum a
485-
ucumName' (Prefixed p n) = (++) <$> nameComponent ucum p <*> nameComponent ucum n
486-
ucumName' (Product n1 n2) = do
487-
n1' <- ucumName' n1
488-
n2' <- ucumName' n2
489-
return $ n1' ++ "." ++ n2'
490-
-- TODO: does one of these subexpressions require a grouping if it is itself a quotient? seems like it must
491-
-- we did it at construction time, but if we are going to expose the constructors then we need to do it again.
492-
ucumName' (Quotient n1 n2) = do
493-
n1' <- ucumName' n1
494-
n2' <- ucumName' n2
495-
return $ n1' ++ "/" ++ n2'
496-
-- TODO #109: note in this case that the UCUM is changing their grammar to not accept exponents after
497-
-- as a result it will become necessary to distribute the exponentiation over the items in the base name
498-
-- prior to generating the UCUM name
499-
ucumName' (Power n x) = do
500-
n' <- ucumName' n
501-
return $ n' ++ show x
502-
ucumName' (Grouped n) = (\x -> "(" ++ x ++ ")") <$> ucumName' n
503-
ucumName' (Weaken n) = ucumName' n
502+
f = UnitNameFold {
503+
foldOne = Just "1"
504+
, foldPrefix = liftA2 (++)
505+
, foldProduct = \n1 n2 -> do
506+
n1' <- n1
507+
n2' <- n2
508+
return $ n1' ++ "." ++ n2'
509+
, foldQuotient = \n1 n2 -> do
510+
n1' <- n1
511+
n2' <- n2
512+
return $ n1' ++ "/" ++ n2'
513+
, foldPower = \n x -> do
514+
n' <- n
515+
return $ n' ++ show x
516+
, foldGrouped = \n -> do
517+
n' <- n
518+
return $ "(" ++ n' ++ ")"
519+
}
504520

505521
prefix :: String -> String -> String -> Int -> Prefix
506522
prefix i a f = Prefix n

tests/Numeric/Units/Dimensional/UnitNamesSpec.hs

+10-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
module Numeric.Units.Dimensional.UnitNamesSpec where
44

55
import Numeric.Units.Dimensional.UnitNames
6-
import Numeric.Units.Dimensional.UnitNames.Internal (UnitName'(..), eliminateOnes, eliminateGrouping, eliminateRedundantPowers)
6+
import Numeric.Units.Dimensional.UnitNames.Internal (UnitName'(..), eliminateOnes, eliminateGrouping, eliminateRedundantPowers, distributePowers)
77
import Numeric.Units.Dimensional.UnitNames.Atoms (atom)
88
import Numeric.Units.Dimensional.UnitNames.Languages
99
import Numeric.Units.Dimensional.Prelude hiding ((*), (/), product, weaken)
@@ -91,9 +91,18 @@ spec = do
9191
let n = name' $ (ampere D.* one D.^ pos3)
9292
let n' = name' $ (ampere D.* one)
9393
eliminateRedundantPowers n `shouldBe`n'
94+
it "eliminates nested exponents" $ do
95+
let n = name' $ ((meter D.^ neg1) D.^ pos2 D.* kilo gram)
96+
let n' = name' $ (meter D.^ neg2 D.* kilo gram)
97+
eliminateRedundantPowers n `shouldBe`n'
9498
it "preserves other exponents" $ do
9599
let n = name' $ (ampere D./ meter D.^ pos2)
96100
eliminateRedundantPowers n `shouldBe`n
101+
describe "with distributePowers" $ do
102+
it "distributes powers to molecules" $ do
103+
let n = name' $ liter D./ D.grouped ((kilo gram D.* meter) D.^ pos2)
104+
let n' = name' $ liter D./ D.grouped (kilo gram D.^pos2 D.* meter D.^ pos2)
105+
distributePowers n `shouldBe` n'
97106
describe "Unit name formatting" $ do
98107
it "formats atomic unit names" $ do
99108
let n = name' ampere

0 commit comments

Comments
 (0)