Ajout : Lib.TreeMap.Zipper : en prévision de collectes « à la XSLT » sur Chart.
[comptalang.git] / lib / Hcompta / Amount.hs
index 529fe1c37ae2fef100b9480f2357724b33ccf219..2428049bdd0506072b06e63c09c0c56e674792a6 100644 (file)
 {-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE TypeSynonymInstances #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 module Hcompta.Amount where
 
-import           Data.Data
-import qualified Data.List
-import qualified Data.Map.Strict as Data.Map
-import qualified Data.Foldable
-import           Data.Typeable ()
-
-import qualified Hcompta.Balance as Balance
-import qualified Hcompta.Amount.Quantity as Quantity
-import qualified Hcompta.Amount.Style as Style
-import qualified Hcompta.Amount.Unit as Unit
-
--- * Type synonyms to submodules
-
-type Quantity = Quantity.Quantity
-type Style    = Style.Style
-type Unit     = Unit.Unit
-
--- * The 'Amount' type
-
-data Amount
- =   Amount
- { quantity :: Quantity
- , style    :: Style
- , unit     :: Unit
- } deriving (Data, Show, Typeable)
-
-instance Eq Amount where
-       (==)
-        Amount{quantity=q0, unit=u0}
-        Amount{quantity=q1, unit=u1} =
-               case compare u0 u1 of
-                LT -> False
-                GT -> False
-                EQ -> (==) q0 q1 -- NOTE: MUST already be rounded to Style.precision
-
-instance Ord Amount where
-       compare
-        Amount{quantity=q0, unit=u0}
-        Amount{quantity=q1, unit=u1} =
-               case compare u0 u1 of
-                LT -> LT
-                GT -> GT
-                EQ -> compare q0 q1 -- NOTE: MUST already be rounded to Style.precision
-
-instance Balance.Amount Amount where
-       type Amount_Unit Amount = Unit
-       amount_null = (==) Quantity.zero . quantity
-       amount_add = (+)
-       amount_negate = negate
-       amount_positive a =
-               case compare (quantity a) Quantity.zero of
-                LT -> Nothing
-                EQ -> Nothing
-                _  -> Just a
-       amount_negative a =
-               case compare (quantity a) Quantity.zero of
-                GT -> Nothing
-                EQ -> Nothing
-                _  -> Just a
-
--- | An 'Amount' is a partially valid 'Num' instance:
---
---   * (+) operator requires both operand to have the same 'Unit' (homogeneity constraint).
---   * (*) operator requires at least one operand with an empty 'Unit' ('scalar').
-instance Num Amount where
-       abs    a@Amount{quantity=q} = a{quantity=abs q}
-       fromInteger                 = scalar . fromInteger
-       negate a@Amount{quantity=q} = a{quantity=negate q}
-       signum a@Amount{quantity=q} = a{quantity=signum q}
-       (+) a b =
-               let s@(Style.Style{Style.precision=p}) = Style.union (style a) (style b) in
-               a{ quantity = Quantity.round p $ quantity a + quantity b
-                , style = s
-                , unit =
-                       if unit a == unit b
-                       then unit a
-                       else error "(+) on non-homogeneous units"
-                }
-       (*) a b =
-               let Style.Style{Style.precision=p} = s in
-               a{ quantity = Quantity.round p $ quantity a * quantity b
-                , style = s
-                , unit = u
-                }
-               where (s, u) =
-                       if unit a == ""
-                       then
-                               if unit b == ""
-                               then (Style.union (style a) (style b), "")
-                               else (style b, unit b)
-                       else
-                               if unit b == ""
-                               then (style a, unit a)
-                               else error "(*) by non-scalar unit"
-
--- ** Constructors
-
-nil :: Amount
-nil =
-       Amount
-        { quantity = Quantity.zero
-        , style = Style.nil
-        , unit = ""
-        }
-
--- *** From 'Quantity'
-
--- | Return an 'Amount' with 'unit' = Unit.'Unit.nil'.
-scalar :: Quantity -> Amount
-scalar q =
-       Amount
-        { quantity = q
-        , style = Style.Style
-                { Style.fractioning = Just '.'
-                , Style.grouping_fractional = Just $ Style.Grouping ',' [3]
-                , Style.grouping_integral   = Just $ Style.Grouping ',' [3]
-                , Style.precision = maxBound
-                , Style.unit_side = Just Style.Side_Right
-                , Style.unit_spaced = Just False
-                }
-        , unit = ""
-        }
-
--- | <https://en.wikipedia.org/wiki/Swiss_franc Swiss franc> unit of currency.
-chf :: Quantity -> Amount
-chf q =
-       Amount
-        { quantity = q
-        , style = Style.Style
-                { Style.fractioning = Just ','
-                , Style.grouping_fractional = Just $ Style.Grouping '.' [3]
-                , Style.grouping_integral   = Just $ Style.Grouping '.' [3]
-                , Style.precision = 2
-                , Style.unit_side = Just Style.Side_Right
-                , Style.unit_spaced = Just False
-                }
-        , unit = "CHF"
-        }
--- | <https://en.wikipedia.org/wiki/Yuan Yuan> unit of currency.
-cny :: Quantity -> Amount
-cny q =
-       Amount
-        { quantity = q
-        , style = Style.Style
-                { Style.fractioning = Just ','
-                , Style.grouping_fractional = Just $ Style.Grouping '.' [3]
-                , Style.grouping_integral   = Just $ Style.Grouping '.' [3]
-                , Style.precision = 2
-                , Style.unit_side = Just Style.Side_Right
-                , Style.unit_spaced = Just False
-                }
-        , unit = "Ұ"
-        }
--- | <https://en.wikipedia.org/wiki/Euro Euro> unit of currency.
-eur :: Quantity -> Amount
-eur q =
-       Amount
-        { quantity = q
-        , style = Style.Style
-                { Style.fractioning = Just ','
-                , Style.grouping_fractional = Just $ Style.Grouping '.' [3]
-                , Style.grouping_integral   = Just $ Style.Grouping '.' [3]
-                , Style.precision = 2
-                , Style.unit_side = Just Style.Side_Right
-                , Style.unit_spaced = Just False
-                }
-        , unit = "€"
-        }
--- | <https://en.wikipedia.org/wiki/Pound_sterling Pound sterling> unit of currency.
-gbp :: Quantity -> Amount
-gbp q =
-       Amount
-        { quantity = q
-        , style = Style.Style
-                { Style.fractioning = Just '.'
-                , Style.grouping_fractional = Just $ Style.Grouping ',' [3]
-                , Style.grouping_integral   = Just $ Style.Grouping ',' [3]
-                , Style.precision = 2
-                , Style.unit_side = Just Style.Side_Left
-                , Style.unit_spaced = Just False
-                }
-        , unit = "£"
-        }
--- | <https://en.wikipedia.org/wiki/Indian_rupee Indian rupee> unit of currency.
-inr :: Quantity -> Amount
-inr q =
-       Amount
-        { quantity = q
-        , style = Style.Style
-                { Style.fractioning = Just ','
-                , Style.grouping_fractional = Just $ Style.Grouping '.' [3]
-                , Style.grouping_integral   = Just $ Style.Grouping '.' [3]
-                , Style.precision = 2
-                , Style.unit_side = Just Style.Side_Right
-                , Style.unit_spaced = Just False
-                }
-        , unit = "₹"
-        }
--- | <https://en.wikipedia.org/wiki/Japanese_yen Japanese yen> unit of currency.
-jpy :: Quantity -> Amount
-jpy q =
-       Amount
-        { quantity = q
-        , style = Style.Style
-                { Style.fractioning = Just '.'
-                , Style.grouping_fractional = Just $ Style.Grouping ',' [3]
-                , Style.grouping_integral   = Just $ Style.Grouping ',' [3]
-                , Style.precision = 2
-                , Style.unit_side = Just Style.Side_Left
-                , Style.unit_spaced = Just False
-                }
-        , unit = "¥"
-        }
--- | <https://en.wikipedia.org/wiki/Russian_ruble Russian ruble> unit of currency.
---
--- NOTE: Ꝑ (U+A750) is used as a replacement latin letter,
---       because GHC currently chokes on ₽ (U+20BD),
---       which is the recently (2014/02) assigned Unicode code-point
---       for this currency.
-rub :: Quantity -> Amount
-rub q =
-       Amount
-        { quantity = q
-        , style = Style.Style
-                { Style.fractioning = Just '.'
-                , Style.grouping_fractional = Just $ Style.Grouping ',' [3]
-                , Style.grouping_integral   = Just $ Style.Grouping ',' [3]
-                , Style.precision = 2
-                , Style.unit_side = Just Style.Side_Left
-                , Style.unit_spaced = Just False
-                }
-        , unit = "Ꝑ"
-        }
--- | <https://en.wikipedia.org/wiki/United_States_dollar United States dollar> unit of currency.
-usd :: Quantity -> Amount
-usd q =
-       Amount
-        { quantity = q
-        , style = Style.Style
-                { Style.fractioning = Just '.'
-                , Style.grouping_fractional = Just $ Style.Grouping ',' [3]
-                , Style.grouping_integral = Just $ Style.Grouping ',' [3]
-                , Style.precision = 2
-                , Style.unit_side = Just Style.Side_Left
-                , Style.unit_spaced = Just False
-                }
-        , unit = "$"
-        }
-
--- ** Tests
-
--- | Return 'True' if and only if 'Amount'’s 'Quantity' is zero.
---
---  NOTE: the 'Amount'’s 'quantity' MUST be have been rounded at 'Amount'’s 'style'’s 'Style.precision'.
-is_zero :: Amount -> Bool
-is_zero = Quantity.is_zero . quantity
-
--- * The 'Amount_by_Unit' mapping
-
-type Amount_by_Unit
- = Data.Map.Map Unit Amount
-type By_Unit = Amount_by_Unit
-
--- | 'Amount_by_Unit'’s is a partially valid 'Num' instance.
---
---   * (*) operator is not defined.
-instance Num Amount_by_Unit where
-       abs         = Data.Map.map (\a@Amount{quantity=q} -> a{quantity=abs    q})
-       fromInteger = Data.Map.singleton "" . fromInteger
-       negate      = Data.Map.map (\a@Amount{quantity=q} -> a{quantity=negate q})
-       signum      = Data.Map.map (\a@Amount{quantity=q} -> a{quantity=signum q})
-       (+)         = Data.Map.unionWith (+)
-       (*)         = error "(*) not-supported"
-
-type Signs = (Int, Int)
-
-signs :: Amount_by_Unit -> Signs
-signs = Data.Map.foldl
- (\(nega, plus) amt ->
-       case flip compare 0 $ quantity amt of
-        LT -> (nega - 1, plus)
-        EQ -> (nega, plus)
-        GT -> (nega, plus + 1))
- (0, 0)
-
--- ** Constructors
-
-nil_By_Unit :: Amount_by_Unit
-nil_By_Unit =
-       Data.Map.empty
-
--- ** Tests
-
--- | Return 'True' if and only if all 'Amount's satisfy 'is_zero'.
-are_zero :: Amount_by_Unit -> Bool
-are_zero = Data.Foldable.all is_zero
-
--- | Return a tuple associating the given 'Amount' with its 'Unit'.
-assoc_by_unit :: Amount -> (Unit, Amount)
-assoc_by_unit amount = (unit amount, amount)
-
--- | Return a 'Data.Map.Map' associating the given 'Amount's with their respective 'Unit'.
-from_List :: [Amount] -> Amount_by_Unit
-from_List amounts =
-       Data.Map.fromListWith (flip (+)) $ -- NOTE: flip to propagate 'Style' first to last.
-       Data.List.map assoc_by_unit amounts
+import           Data.Data (Data)
+import           Data.Eq (Eq(..))
+import           Data.Functor (Functor(..))
+import           Data.Ord (Ord(..), Ordering)
+import           Data.Tuple (fst, snd)
+import           Prelude (($), (.))
+
+import           Hcompta.Polarize
+import           Hcompta.Quantity
+import           Hcompta.Unit (Unit(..))
+
+-- * Class 'Amount'
+
+class
+ ( Data a
+ , Eq   (Amount_Quantity a)
+ , Zero (Amount_Quantity a)
+ , Unit (Amount_Unit     a)
+ ) =>  Amount          a where
+       type Amount_Quantity a
+       type Amount_Unit     a
+       amount_quantity :: a -> Amount_Quantity a
+       amount_unit     :: a -> Amount_Unit     a
+instance
+ ( Zero quantity
+ , Eq   quantity
+ , Unit unit
+ ) => Amount (unit, quantity) where
+       type Amount_Quantity (unit, quantity) = quantity
+       type Amount_Unit     (unit, quantity) = unit
+       amount_quantity = snd
+       amount_unit     = fst
+instance
+ ( Polarizable quantity
+ ) => Polarizable (unit, quantity) where
+       polarizable_positive (u, q) = fmap (u,) $ polarizable_positive q
+       polarizable_negative (u, q) = fmap (u,) $ polarizable_negative q
+
+amount_sign ::
+ ( Amount a
+ , Ord (Amount_Quantity a)
+ ) => a -> Ordering
+amount_sign =
+       quantity_sign . amount_quantity