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.nil
74 -- *** From 'Quantity'
76 -- | Return an 'Amount' with 'unit' = Unit.'Unit.nil'.
77 scalar :: Quantity -> Amount
80 { conversion = Nothing
83 { Style.fractioning = Just '.'
84 , Style.grouping_fractional = Just $ Style.Grouping ',' [3]
85 , Style.grouping_integral = Just $ Style.Grouping ',' [3]
86 , Style.precision = maxBound
87 , Style.unit_side = Just Style.Side_Right
88 , Style.unit_spaced = Just False
93 -- | <https://en.wikipedia.org/wiki/Swiss_franc Swiss franc> unit of currency.
94 chf :: Quantity -> Amount
97 { conversion = Nothing
100 { Style.fractioning = Just ','
101 , Style.grouping_fractional = Just $ Style.Grouping '.' [3]
102 , Style.grouping_integral = Just $ Style.Grouping '.' [3]
103 , Style.precision = 2
104 , Style.unit_side = Just Style.Side_Right
105 , Style.unit_spaced = Just False
109 -- | <https://en.wikipedia.org/wiki/Yuan Yuan> unit of currency.
110 cny :: Quantity -> Amount
113 { conversion = Nothing
115 , style = Style.Style
116 { Style.fractioning = Just ','
117 , Style.grouping_fractional = Just $ Style.Grouping '.' [3]
118 , Style.grouping_integral = Just $ Style.Grouping '.' [3]
119 , Style.precision = 2
120 , Style.unit_side = Just Style.Side_Right
121 , Style.unit_spaced = Just False
125 -- | <https://en.wikipedia.org/wiki/Euro Euro> unit of currency.
126 eur :: Quantity -> Amount
129 { conversion = Nothing
131 , style = Style.Style
132 { Style.fractioning = Just ','
133 , Style.grouping_fractional = Just $ Style.Grouping '.' [3]
134 , Style.grouping_integral = Just $ Style.Grouping '.' [3]
135 , Style.precision = 2
136 , Style.unit_side = Just Style.Side_Right
137 , Style.unit_spaced = Just False
141 -- | <https://en.wikipedia.org/wiki/Pound_sterling Pound sterling> unit of currency.
142 gbp :: Quantity -> Amount
145 { conversion = Nothing
147 , style = Style.Style
148 { Style.fractioning = Just '.'
149 , Style.grouping_fractional = Just $ Style.Grouping ',' [3]
150 , Style.grouping_integral = Just $ Style.Grouping ',' [3]
151 , Style.precision = 2
152 , Style.unit_side = Just Style.Side_Left
153 , Style.unit_spaced = Just False
157 -- | <https://en.wikipedia.org/wiki/Indian_rupee Indian rupee> unit of currency.
158 inr :: Quantity -> Amount
161 { conversion = Nothing
163 , style = Style.Style
164 { Style.fractioning = Just ','
165 , Style.grouping_fractional = Just $ Style.Grouping '.' [3]
166 , Style.grouping_integral = Just $ Style.Grouping '.' [3]
167 , Style.precision = 2
168 , Style.unit_side = Just Style.Side_Right
169 , Style.unit_spaced = Just False
173 -- | <https://en.wikipedia.org/wiki/Japanese_yen Japanese yen> unit of currency.
174 jpy :: Quantity -> Amount
177 { conversion = Nothing
179 , style = Style.Style
180 { Style.fractioning = Just '.'
181 , Style.grouping_fractional = Just $ Style.Grouping ',' [3]
182 , Style.grouping_integral = Just $ Style.Grouping ',' [3]
183 , Style.precision = 2
184 , Style.unit_side = Just Style.Side_Left
185 , Style.unit_spaced = Just False
189 -- | <https://en.wikipedia.org/wiki/Russian_ruble Russian ruble> unit of currency.
191 -- NOTE: Ꝑ (U+A750) is used as a replacement latin letter,
192 -- because GHC currently chokes on ₽ (U+20BD),
193 -- which is the recently (2014/02) assigned Unicode code-point
194 -- for this currency.
195 rub :: Quantity -> Amount
198 { conversion = Nothing
200 , style = Style.Style
201 { Style.fractioning = Just '.'
202 , Style.grouping_fractional = Just $ Style.Grouping ',' [3]
203 , Style.grouping_integral = Just $ Style.Grouping ',' [3]
204 , Style.precision = 2
205 , Style.unit_side = Just Style.Side_Left
206 , Style.unit_spaced = Just False
210 -- | <https://en.wikipedia.org/wiki/United_States_dollar United States dollar> unit of currency.
211 usd :: Quantity -> Amount
214 { conversion = Nothing
216 , style = Style.Style
217 { Style.fractioning = Just '.'
218 , Style.grouping_fractional = Just $ Style.Grouping ',' [3]
219 , Style.grouping_integral = Just $ Style.Grouping ',' [3]
220 , Style.precision = 2
221 , Style.unit_side = Just Style.Side_Left
222 , Style.unit_spaced = Just False
229 -- | Return 'True' if and only if 'Amount'’s 'Quantity' is zero at 'Style'’s precision.
230 is_zero :: Amount -> Bool
233 (Style.precision $ style amount) $
236 -- * The 'By_Unit' mapping
239 = Data.Map.Map Unit Amount
241 -- | 'By_Unit'’s is a partially valid 'Num' instance.
243 -- * (*) operator is not defined.
244 instance Num By_Unit where
245 abs = Data.Map.map (\a@Amount{quantity=q} -> a{quantity=abs q})
246 fromInteger = Data.Map.singleton "" . fromInteger
247 negate = Data.Map.map (\a@Amount{quantity=q} -> a{quantity=negate q})
248 signum = Data.Map.map (\a@Amount{quantity=q} -> a{quantity=signum q})
249 (+) = Data.Map.unionWith (+)
250 (*) = error "(*) not-supported"
254 nil_By_Unit :: By_Unit
260 -- | Return 'True' if and only if all 'Amount's are zero at their 'Style'’s precision.
261 are_zero :: By_Unit -> Bool
262 are_zero = Data.Foldable.all is_zero
264 -- | Return a tuple associating the given 'Amount' with its 'Unit'.
265 assoc_by_unit :: Amount -> (Unit, Amount)
266 assoc_by_unit amount = (unit amount, amount)
268 -- | Return a 'Data.Map.Map' associating the given 'Amount's with their respective 'Unit'.
269 from_List :: [Amount] -> By_Unit
271 Data.Map.fromListWith (+) $
272 Data.List.map assoc_by_unit amounts