1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE FlexibleInstances #-}
3 {-# LANGUAGE TypeSynonymInstances #-}
4 {-# OPTIONS_GHC -fno-warn-orphans #-}
5 module Hcompta.Model.Amount where
8 import qualified Data.List
9 import qualified Data.Map
10 import qualified Data.Foldable
11 import Data.Typeable ()
13 import qualified Hcompta.Model.Amount.Conversion as Conversion
14 import qualified Hcompta.Model.Amount.Quantity as Quantity
15 import qualified Hcompta.Model.Amount.Style as Style
16 import qualified Hcompta.Model.Amount.Unit as Unit
18 -- * Type synonyms to submodules
20 type Conversion = Conversion.Conversion
21 type Quantity = Quantity.Quantity
22 type Style = Style.Style
25 -- * The 'Amount' type
29 { conversion :: Maybe Conversion
30 , quantity :: Quantity
33 } deriving (Data, Eq, Ord, Read, Show, Typeable)
35 -- | An 'Amount' is a partially valid 'Num' instance:
37 -- * (+) operator requires both operand to have the same 'Unit' (homogeneity constraint).
38 -- * (*) operator requires at least one operand with an empty 'Unit' ('scalar').
39 instance Num Amount where
40 abs a@Amount{quantity=q} = a{quantity=abs q}
41 fromInteger = scalar . fromInteger
42 negate a@Amount{quantity=q} = a{quantity=negate q}
43 signum a@Amount{quantity=q} = a{quantity=signum q}
44 (+) a b = a{ quantity=quantity a + quantity b
45 , style=Style.union (style a) (style b)
46 , unit=if unit a == unit b then unit a else error "(+) on non-homogeneous units"
48 (*) a b = a{ quantity=quantity a * quantity b
56 then (Style.union (style a) (style b), "")
57 else (style b, unit b)
60 then (style a, unit a)
61 else error "(*) by non-scalar unit"
68 { conversion = Nothing
69 , quantity = Quantity.null
74 --- *** From 'Quantity'
76 -- | Return an empty 'Unit' 'Amount'.
77 scalar :: Quantity -> Amount
83 { Style.decimal_point=Just '.'
84 , Style.format=Just $ Style.Format ',' [3]
85 , Style.precision=maxBound
86 , Style.unit_side=Just Style.Side_Right
87 , Style.unit_spaced=Just False
92 -- | <https://en.wikipedia.org/wiki/Swiss_franc Swiss franc> unit of currency.
93 chf :: Quantity -> Amount
99 { Style.decimal_point=Just ','
100 , Style.format=Just $ Style.Format '.' [3]
102 , Style.unit_side=Just Style.Side_Right
103 , Style.unit_spaced=Just False
107 -- | <https://en.wikipedia.org/wiki/Yuan Yuan> unit of currency.
108 cny :: Quantity -> Amount
114 { Style.decimal_point=Just ','
115 , Style.format=Just $ Style.Format '.' [3]
117 , Style.unit_side=Just Style.Side_Right
118 , Style.unit_spaced=Just False
122 -- | <https://en.wikipedia.org/wiki/Euro Euro> unit of currency.
123 eur :: Quantity -> Amount
129 { Style.decimal_point=Just ','
130 , Style.format=Just $ Style.Format '.' [3]
132 , Style.unit_side=Just Style.Side_Right
133 , Style.unit_spaced=Just False
137 -- | <https://en.wikipedia.org/wiki/Pound_sterling Pound sterling> unit of currency.
138 gbp :: Quantity -> Amount
144 { Style.decimal_point=Just '.'
145 , Style.format=Just $ Style.Format ',' [3]
147 , Style.unit_side=Just Style.Side_Left
148 , Style.unit_spaced=Just False
152 -- | <https://en.wikipedia.org/wiki/Indian_rupee Indian rupee> unit of currency.
153 inr :: Quantity -> Amount
159 { Style.decimal_point=Just ','
160 , Style.format=Just $ Style.Format '.' [3]
162 , Style.unit_side=Just Style.Side_Right
163 , Style.unit_spaced=Just False
167 -- | <https://en.wikipedia.org/wiki/Japanese_yen Japanese yen> unit of currency.
168 jpy :: Quantity -> Amount
174 { Style.decimal_point=Just '.'
175 , Style.format=Just $ Style.Format ',' [3]
177 , Style.unit_side=Just Style.Side_Left
178 , Style.unit_spaced=Just False
182 -- | <https://en.wikipedia.org/wiki/Russian_ruble Russian ruble> unit of currency.
184 -- NOTE: Ꝑ (U+A750) is used as a replacement latin letter,
185 -- because GHC currently chokes on ₽ (U+20BD),
186 -- which is the recently (2014/02) assigned Unicode code-point
187 -- for this currency.
188 rub :: Quantity -> Amount
194 { Style.decimal_point=Just '.'
195 , Style.format=Just $ Style.Format ',' [3]
197 , Style.unit_side=Just Style.Side_Left
198 , Style.unit_spaced=Just False
202 -- | <https://en.wikipedia.org/wiki/United_States_dollar United States dollar> unit of currency.
203 usd :: Quantity -> Amount
209 { Style.decimal_point=Just '.'
210 , Style.format=Just $ Style.Format ',' [3]
212 , Style.unit_side=Just Style.Side_Left
213 , Style.unit_spaced=Just False
220 -- | Return 'True' if and only if 'Amount'’s 'Quantity' is zero at 'Style'’s precision.
221 is_zero :: Amount -> Bool
224 (Style.precision $ style amount) $
227 -- * The 'By_Unit' mapping
229 -- | 'By_Unit'’s is a partially valid 'Num' instance:
231 -- * (*) operator is not defined.
233 = Data.Map.Map Unit Amount
235 -- XXX: haddock drops this instance
236 -- | 'By_Unit'’s is a partially valid 'Num' instance.
237 instance Num By_Unit where
238 abs = Data.Map.map (\a@Amount{quantity=q} -> a{quantity=abs q})
239 fromInteger = Data.Map.singleton "" . fromInteger
240 negate = Data.Map.map (\a@Amount{quantity=q} -> a{quantity=negate q})
241 signum = Data.Map.map (\a@Amount{quantity=q} -> a{quantity=signum q})
242 (+) = Data.Map.unionWith (+)
243 (*) = error "(*) not-supported"
247 null_By_Unit :: By_Unit
253 -- | Return 'True' if and only if all 'Amount's are zero at their 'Style'’s precision.
254 are_zero :: By_Unit -> Bool
255 are_zero = Data.Foldable.all is_zero
257 -- | Return a tuple associating the given 'Amount' with its 'Unit'.
258 assoc_by_unit :: Amount -> (Unit, Amount)
259 assoc_by_unit amount = (unit amount, amount)
261 -- | Return a 'Data.Map.Map' associating the given 'Amount's with their respective 'Unit'.
262 from_List :: [Amount] -> By_Unit
264 Data.Map.fromListWith (+) $
265 Data.List.map assoc_by_unit amounts