packages feed

safe-money 0.4.1 → 0.5

raw patch · 7 files changed

+2489/−1607 lines, 7 filesdep +vector-spacePVP ok

version bump matches the API change (PVP)

Dependencies added: vector-space

API changes (from Hackage documentation)

- Money: ceiling :: GoodScale scale => Dense currency -> (Discrete' currency scale, Dense currency)
- Money: flipExchangeRate :: ExchangeRate a b -> ExchangeRate b a
- Money: floor :: GoodScale scale => Dense currency -> (Discrete' currency scale, Dense currency)
- Money: fromDiscrete :: GoodScale scale => Discrete' currency scale -> Dense currency
- Money: fromExchangeRate :: ExchangeRate src dst -> Rational
- Money: round :: GoodScale scale => Dense currency -> (Discrete' currency scale, Dense currency)
- Money: truncate :: GoodScale scale => Dense currency -> (Discrete' currency scale, Dense currency)
+ Money: Ceiling :: Approximation
+ Money: Floor :: Approximation
+ Money: Round :: Approximation
+ Money: Truncate :: Approximation
+ Money: data Approximation
+ Money: dense' :: Rational -> Dense currency
+ Money: denseCurrency :: KnownSymbol currency => Dense currency -> String
+ Money: denseFromDecimal :: Maybe Char -> Char -> String -> Maybe (Dense currency)
+ Money: denseFromDiscrete :: GoodScale scale => Discrete' currency scale -> Dense currency
+ Money: denseToDecimal :: GoodScale scale => Approximation -> Bool -> Maybe Char -> Char -> Word8 -> Proxy scale -> Dense currency -> Maybe String
+ Money: discrete :: GoodScale scale => Integer -> Discrete' currency scale
+ Money: discreteCurrency :: forall currency scale. (KnownSymbol currency, GoodScale scale) => Discrete' currency scale -> String
+ Money: discreteFromDecimal :: GoodScale scale => Maybe Char -> Char -> String -> Maybe (Discrete' currency scale)
+ Money: discreteFromDense :: forall currency scale. GoodScale scale => Approximation -> Dense currency -> (Discrete' currency scale, Dense currency)
+ Money: exchangeRateFromDecimal :: Maybe Char -> Char -> String -> Maybe (ExchangeRate src dst)
+ Money: exchangeRateRecip :: ExchangeRate a b -> ExchangeRate b a
+ Money: exchangeRateToDecimal :: Approximation -> Maybe Char -> Char -> Word8 -> ExchangeRate src dst -> Maybe String
+ Money: exchangeRateToRational :: ExchangeRate src dst -> Rational
- Money: type GoodScale (scale :: (Nat, Nat)) = (CmpNat 0 (Fst scale) ~ LT, CmpNat 0 (Snd scale) ~ LT, KnownNat (Fst scale), KnownNat (Snd scale))
+ Money: type GoodScale (scale :: (Nat, Nat)) = (CmpNat 0 (Fst scale) ~ 'LT, CmpNat 0 (Snd scale) ~ 'LT, KnownNat (Fst scale), KnownNat (Snd scale))

Files

LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2016-2017, Renzo Carbonara+Copyright (c) 2016-2018, Renzo Carbonara  All rights reserved. 
README.md view
@@ -2,5 +2,5 @@ operations for monetary values in all world currencies, including fiat currencies, precious metals and crypto-currencies. -You can enter a development shell with `nix-shell`, or build with `nix-build-./release.nix -A ghc822.safe-money` (or similar, check `release.nix`).+You can enter a development shell with `nix-shell`, or build with `nix build+-f release.nix ghc822.safe-money` (or similar, check `release.nix`).
changelog.md view
@@ -1,3 +1,47 @@+# Version 0.5++* _COMPILER ASSISTED BREAKING CHANGE_. The `round`, `floor`, `ceiling` and+  `truncate` functions were replaced by a single `discreteFromDense` function+  taking an argument of type `Approximation` (`Round`, `Floor`, `Ceiling` or+  `Truncate`) as an argument.++* _COMPILER ASSISTED BREAKING CHANGE_. The `fromDiscrete` function was+  renamed to `denseFromDiscrete`.++* _COMPILER ASSISTED BREAKING CHANGE_. The `fromExchangeRate` function was+  renamed to `exchangeRateToRational`.++* _COMPILER ASSISTED BREAKING CHANGE_. The `flipExchangeRate` function was+  renamed to `exchangeRateRecip`.++* _COMPILER ASSISTED BREAKING CHANGE_. The `Dense` is not an instance of+  `Fractional` anymore because `recip` and `/` could potentially crash.++* Introduced new functions for rendering and parsing decimal reperesentations+  of monetary amounts: `denseCurrency`, `discreteCurrency`, `denseFromDecimal`,+  `denseToDecimal`, `discreteFromDecimal`, `exchangeRateFromDecimal`,+  `exchangeRateToDecimal`.++* Introduced optional `AdditiveGroup` and `VectorSpace` group instances for+  `Dense` and `Discrete`. These type-classes come from the `vector-space`+  library and they can be enabled or disabled via the `vector-space` Cabal flag,+  which is enabled by default.++* Introduced `discrete` constructor which behaves just like `fromInteger`.++* Introduced the `dense'` constructor as an unsafe but convenient version of+  `dense`.++* The `ErrScaleNonCanonical` type is now exported.++* The `Money.Internal` module is now exposed, but hidden from the Haddock+  documentation.++* New scale: `"BTC" "millibitcoin"`.++* Added many tests.++ # Version 0.4.1  * `ExchangeRate` is now a `Category`.@@ -5,26 +49,25 @@  # Version 0.4 -* BREAKING CHANGE. COMPILER WON'T COMPLAIN. HUMAN INTERVENTION POTENTIALLY-  REQUIRED. The JSON serializations for all of `Dense`, `SomeDense`, `Discrete`,-  `SomeDiscrete`, `ExchangeRate` and `SomeExchangeRate` changed. The `FromJSON`-  instances are backwards compatible with the old serializations, but the-  `ToJSON` instances will only generate the new format, which is the same as the-  old format except the leading strings `"Dense"`, `"Discrete"` and-  `"ExchangeRate"`, respectively, are not present in the rendered JSON array-  anymore. So, if you were manually relying on the `ToJSON` instance, please-  update your code.+* **BREAKING CHANGE REQUIRING HUMAN INTERVENTION**. The JSON serializations for+  all of `Dense`, `SomeDense`, `Discrete`, `SomeDiscrete`, `ExchangeRate` and+  `SomeExchangeRate` changed. The `FromJSON` instances are backwards compatible+  with the old serializations, but the `ToJSON` instances will only generate the+  new format, which is the same as the old format except the leading strings+  `"Dense"`, `"Discrete"` and `"ExchangeRate"`, respectively, are not present in+  the rendered JSON array anymore. So, if you were manually relying on the+  `ToJSON` instance, please update your code. -* BREAKING CHANGE. COMPILER WILL COMPLAIN: Changed the `Rep` suffix for a `Some`+* _COMPILER ASSISTED BREAKING CHANGE_. Changed the `Rep` suffix for a `Some`   prefix Everywhere.  For example, `DenseRep` was renamed to `SomeDense`. -* BREAKING CHANGE. COMPILER WILL COMPLAIN: Replaced the+* _COMPILER ASSISTED BREAKING CHANGE_. Replaced the   `someDenseAmountNumerator` and `someDenseAmountDenominator` `Integers` with a   single `someDenseAmount` `Rational` number. Similarly for `someDiscreteScale`   and `someExchangeRateRate`. The `mkSomeDense`, `someDiscreteScale` and   `mkSomeDense` also take a `Rational` now. -* BREAKING CHANGE. COMPILER WILL COMPLAIN: The `truncate`, `floor`, `celing` and+* _COMPILER ASSISTED BREAKING CHANGE_. The `truncate`, `floor`, `celing` and   `round` functions now return just `0` as a reminder if there's no significant   reminder, instead of `Nothing`. @@ -40,18 +83,22 @@  # Version 0.3 -* BREAKING CHANGE: The `Data.Money` module was renamed to `Money`.+* _COMPILER ASSISTED BREAKING CHANGE_. The `Data.Money` module was renamed to+  `Money`. -* BREAKING CHANGE: The `Data.Money.Internal` module is not exposed anymore. All-  of its contents are now exported from the `Money` module.+* _COMPILER ASSISTED BREAKING CHANGE_. The `Data.Money.Internal` module is not+  exposed anymore. All of its contents are now exported from the `Money` module. -* BREAKING CHANGE: Renamed `discreteRep` to `toDiscreteRep`.+* _COMPILER ASSISTED BREAKING CHANGE_. Renamed `discreteRep` to+  `toDiscreteRep`. -* BREAKING CHANGE: Renamed `denseRep` to `toDenseRep`.+* _COMPILER ASSISTED BREAKING CHANGE_. Renamed `denseRep` to `toDenseRep`. -* BREAKING CHANGE: Renamed `exchangeRateRep` to `toExchangeRateRep`.+* _COMPILER ASSISTED BREAKING CHANGE_. Renamed `exchangeRateRep` to+  `toExchangeRateRep`. -* BREAKING CHANGE: Renamed Iceleandic currency `"eyir"` to `"eyrir"`+* _COMPILER ASSISTED BREAKING CHANGE_. Renamed Iceleandic currency `"eyir"` to+  `"eyrir"`  * Remove upper bound constraints from all dependencies except `base`. 
safe-money.cabal view
@@ -1,8 +1,8 @@ name: safe-money-version: 0.4.1+version: 0.5 license: BSD3 license-file: LICENSE-copyright: Copyright (c) Renzo Carbonara 2016-2017+copyright: Copyright (c) Renzo Carbonara 2016-2018 author: Renzo Carbonara maintainer: renλren!zone stability: Experimental@@ -33,9 +33,10 @@   default-language: Haskell2010   hs-source-dirs: src   ghc-options: -Wall -O2-  exposed-modules: Money-  other-modules: Money.Internal   build-depends: base (>=4.8 && <5.0), constraints+  exposed-modules:+    Money+    Money.Internal    if flag(aeson)     build-depends: aeson (>=0.9)@@ -58,6 +59,9 @@   if (flag(store) && !impl(ghcjs))     build-depends: store (>=0.2)     cpp-options: -DHAS_store+  if flag(vector-space)+    build-depends: vector-space (>=0.12)+    cpp-options: -DHAS_vector_space   if flag(xmlbf)     build-depends: xmlbf (>=0.2), text     cpp-options: -DHAS_xmlbf@@ -97,6 +101,9 @@   if (flag(store) && !impl(ghcjs))     build-depends: store     cpp-options: -DHAS_store+  if flag(vector-space)+    build-depends: vector-space+    cpp-options: -DHAS_vector_space   if flag(xmlbf)     build-depends: xmlbf, text     cpp-options: -DHAS_xmlbf@@ -127,6 +134,10 @@   manual: True flag serialise   description: Provide instances for @serialise@+  default: True+  manual: True+flag vector-space+  description: Provide instances for @vector-space@   default: True   manual: True flag xmlbf
src/Money.hs view
@@ -17,29 +17,39 @@ -- -- This module offers plenty of documentation, but for a deep explanation of -- how all of the pieces fit together, please read--- https://ren.zone/articles/safe-money+-- <https://ren.zone/articles/safe-money>. Notice, however, that this library+-- has changed a bit since that article was written. You can always see the+-- [change log](https://github.com/k0001/safe-money/blob/master/changelog.md) to+-- understand what has changed. module Money  ( -- * Dense monetary values    I.Dense+ , I.denseCurrency  , I.dense+ , I.dense'+ , I.denseFromDiscrete+ , I.denseFromDecimal+ , I.denseToDecimal    -- * Discrete monetary values  , I.Discrete  , I.Discrete'- , I.fromDiscrete- , I.round- , I.ceiling- , I.floor- , I.truncate+ , I.discrete+ , I.discreteCurrency+ , I.discreteFromDense+ , I.discreteFromDecimal    -- * Currency scales  , I.Scale  , I.GoodScale+ , I.ErrScaleNonCanonical  , I.scale    -- * Currency exchange  , I.ExchangeRate  , I.exchangeRate- , I.fromExchangeRate- , I.flipExchangeRate  , I.exchange+ , I.exchangeRateRecip+ , I.exchangeRateFromDecimal+ , I.exchangeRateToDecimal+ , I.exchangeRateToRational    -- * Serializable representations  , I.SomeDense  , I.toSomeDense@@ -64,6 +74,8 @@  , I.someExchangeRateSrcCurrency  , I.someExchangeRateDstCurrency  , I.someExchangeRateRate+ -- * Misc+ , I.Approximation(Round, Floor, Ceiling, Truncate)  ) where  import qualified Money.Internal as I@@ -768,6 +780,7 @@ -- | Bitcoin type instance I.Scale "BTC" "BTC" = '(100000000, 1) type instance I.Scale "BTC" "bitcoin" = '(1, 1)+type instance I.Scale "BTC" "millibitcoin" = '(1000, 1) type instance I.Scale "BTC" "satoshi" = '(100000000, 1)  -- | Bitcoin
src/Money/Internal.hs view
@@ -4,1536 +4,1906 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-}--#if MIN_VERSION_base(4,9,0)-{-# OPTIONS_GHC -Wno-redundant-constraints #-}-#endif---- | This is an internal module. Import "Money" instead.-module Money.Internal- ( -- * Dense monetary values-   Dense- , dense-   -- * Discrete monetary values- , Discrete- , Discrete'- , fromDiscrete- , round- , ceiling- , floor- , truncate-   -- * Currency scales- , Scale- , GoodScale- , ErrScaleNonCanonical- , scale-   -- * Currency exchange- , ExchangeRate- , exchangeRate- , fromExchangeRate- , flipExchangeRate- , exchange-   -- * Serializable representations- , SomeDense- , toSomeDense- , mkSomeDense- , fromSomeDense- , withSomeDense- , someDenseCurrency- , someDenseAmount- , SomeDiscrete- , toSomeDiscrete- , mkSomeDiscrete- , fromSomeDiscrete- , withSomeDiscrete- , someDiscreteCurrency- , someDiscreteScale- , someDiscreteAmount- , SomeExchangeRate- , toSomeExchangeRate- , mkSomeExchangeRate- , fromSomeExchangeRate- , withSomeExchangeRate- , someExchangeRateSrcCurrency- , someExchangeRateDstCurrency- , someExchangeRateRate- ) where--import Control.Applicative ((<|>), empty)-import Control.Category (Category((.), id))-import Control.Monad ((<=<), guard, when)-import Data.Constraint (Dict(Dict))-import Data.Monoid ((<>))-import Data.Proxy (Proxy(..))-import Data.Ratio ((%), numerator, denominator)-import GHC.Exts (fromList)-import qualified GHC.Generics as GHC-import GHC.Real (infinity, notANumber)-import GHC.TypeLits-  (Symbol, SomeSymbol(..), Nat, SomeNat(..), CmpNat, KnownSymbol, KnownNat,-   natVal, someNatVal, symbolVal, someSymbolVal)-import Prelude hiding ((.), round, ceiling, floor, truncate)-import qualified Prelude-import qualified Text.ParserCombinators.ReadPrec as ReadPrec-import qualified Text.ParserCombinators.ReadP as ReadP-import qualified Text.Read as Read-import Unsafe.Coerce (unsafeCoerce)--#ifdef HAS_aeson-import qualified Data.Aeson as Ae-#endif--#ifdef HAS_binary-import qualified Data.Binary as Binary-#endif--#ifdef HAS_cereal-import qualified Data.Serialize as Cereal-#endif--#ifdef HAS_deepseq-import Control.DeepSeq (NFData)-#endif--#ifdef HAS_hashable-import Data.Hashable (Hashable)-#endif--#ifdef HAS_serialise-import qualified Codec.Serialise as Ser-#endif--#ifdef HAS_store-import qualified Data.Store as Store-#endif--#ifdef HAS_xmlbf-import qualified Xmlbf-import qualified Data.Text as Text-#endif--#if MIN_VERSION_base(4,9,0)-import qualified GHC.TypeLits as GHC-#endif------------------------------------------------------------------------------------- | 'Dense' represents a dense monetary value for @currency@ (usually a--- ISO-4217 currency code, but not necessarily) as a rational number.------ While monetary values associated with a particular currency are discrete, you--- can still treat monetary values as dense while operating on them. For--- example, the half of @USD 3.41@ is @USD 1.705@, which is not an amount that--- can't be represented as a number of USD cents (the smallest unit that can--- represent USD amounts). Nevertheless, if you eventually multiply @USD 1.705@--- by @4@, for example, you end up with @USD 6.82@, which is again a value--- representable as USD cents. In other words, 'Dense' monetary values--- allow us to perform precise calculations deferring the conversion to a--- 'Discrete' monetary values as much as posible. Once you are ready to--- aproximate a 'Dense' value to a 'Discrete' value you can use one of--- 'round', 'floor', 'ceiling' or 'truncate'. Otherwise, using 'toRational' you--- can obtain a precise 'Rational' representation.------ Construct 'Dense' monetary values using 'dense', 'fromRational',--- 'fromInteger' or 'fromIntegral'.------ /WARNING/ if you want to treat a dense monetary value as a /Real/ number (for--- example, to take the square root of that monetary value), then you are on--- your own. We can only guarantee lossless manipulation of rational values, so--- you will need to convert back and forth betwen the 'Rational' representation--- for 'Dense' and your (likely lossy) representation for /Real/ numbers.-newtype Dense (currency :: Symbol) = Dense Rational-  deriving (Eq, Ord, Num, Real, GHC.Generic)---- | /WARNING/ if there exists the possibility that the given 'Rational' has a--- zero as a denominator, which although unlikely, is possible if the 'Rational'--- is constructed unsafely using 'GHC.Real.infinity' or 'GHC.Real.notANumber'--- for example, then use 'dense' instead of 'fromRational'.-deriving instance Fractional (Dense (currency :: Symbol))--instance forall currency. KnownSymbol currency => Show (Dense currency) where-  showsPrec n = \(Dense r0) ->-    let c = symbolVal (Proxy :: Proxy currency)-    in showParen (n > 10) $-         showString "Dense " . showsPrec 0 c . showChar ' ' .-         showsPrec 0 (numerator r0) . showChar '%' .-         showsPrec 0 (denominator r0)--instance forall currency. KnownSymbol currency => Read (Dense currency) where-  readPrec = Read.parens $ do-    let c = symbolVal (Proxy :: Proxy currency)-    _ <- ReadPrec.lift (ReadP.string ("Dense " ++ show c ++ " "))-    maybe empty pure =<< fmap dense Read.readPrec---- | Build a 'Dense' monetary value from a 'Rational' value.------ For example, if you want to represent @USD 12.52316@, then you can use:------ @--- 'dense' (125316 % 10000)--- @------ Returns 'Nothing' in case the denominator of the given 'Rational' is zero,--- which although unlikely, is possible if the 'Rational' is constructed--- unsafely using 'GHC.Real.infinity' or 'GHC.Real.notANumber', for example.--- If you don't care about that scenario, you can use `fromRational` to build--- the `Dense` value.-dense :: Rational -> Maybe (Dense currency)-dense = \r0 -> if (denominator r0 == 0) then Nothing else Just (Dense r0)-{-# INLINABLE dense #-}---- | 'Discrete' represents a discrete monetary value for a @currency@ expresed--- as an integer amount of a particular @unit@. For example, with @currency ~--- \"USD\"@ and @unit ~ \"cent\"@ you can represent United States Dollars to--- their full extent.------ @currency@ is usually a ISO-4217 currency code, but not necessarily.------ Construct 'Discrete' values using 'fromInteger'.------ For example, if you want to represent @GBP 21.05@, where the smallest--- represetable unit for a GBP (United Kingdom Pound) is the /penny/, and 100--- /pennies/ equal 1 GBP (i.e., @'Scale' \"GBP\" ~ '(100, 1)@), then you can--- use:------ @--- 'fromInteger' 2105 :: Discrete \"GBP\" \"penny\"--- @------ Because @2015 / 100 == 20.15@.-type Discrete (currency :: Symbol) (unit :: Symbol)-  = Discrete' currency (Scale currency unit)---- | 'Discrete'' represents a discrete monetary value for a @currency@ expresed--- as an amount of @scale@, which is a rational number expressed as @(numerator,--- denominator)@.------ You'll be using 'Discrete' instead of 'Discrete'' most of the time, which--- mentions the unit name (such as /cent/ or /centavo/) instead of explicitely--- mentioning the unit scale.-newtype Discrete' (currency :: Symbol) (scale :: (Nat, Nat))-  = Discrete Integer--deriving instance GoodScale scale => Eq (Discrete' currency scale)-deriving instance GoodScale scale => Ord (Discrete' currency scale)-deriving instance GoodScale scale => Enum (Discrete' currency scale)-deriving instance GoodScale scale => Num (Discrete' currency scale)-deriving instance GoodScale scale => Real (Discrete' currency scale)-deriving instance GoodScale scale => Integral (Discrete' currency scale)-deriving instance GoodScale scale => GHC.Generic (Discrete' currency scale)--instance forall currency scale.-  ( KnownSymbol currency, GoodScale scale-  ) => Show (Discrete' currency scale) where-  showsPrec n = \d0@(Discrete i0) ->-    let c = symbolVal (Proxy :: Proxy currency)-        s = scale d0-    in showParen (n > 10) $-         showString "Discrete " .  showsPrec 0 c . showChar ' ' .-         showsPrec 0 (numerator s) . showChar '%' .-         showsPrec 0 (denominator s) . showChar ' ' .-         showsPrec 0 i0--instance forall currency scale.-  ( KnownSymbol currency, GoodScale scale-  ) => Read (Discrete' currency scale) where-  readPrec = Read.parens $ do-    let c = symbolVal (Proxy :: Proxy currency)-        s = scale (Proxy :: Proxy scale)-    _ <- ReadPrec.lift (ReadP.string (concat-           [ "Discrete ", show c, " "-           , show (numerator s), "%"-           , show (denominator s), " "-           ]))-    Discrete <$> Read.readPrec--#if MIN_VERSION_base(4,9,0)-instance-  ( GHC.TypeError-      (('GHC.Text "The ") 'GHC.:<>:-       ('GHC.ShowType Discrete') 'GHC.:<>:-       ('GHC.Text " type is deliberately not a ") 'GHC.:<>:-       ('GHC.ShowType Fractional) 'GHC.:$$:-       ('GHC.Text "instance. Convert the ") 'GHC.:<>:-       ('GHC.ShowType Discrete') 'GHC.:<>:-       ('GHC.Text " value to a ") 'GHC.:<>:-       ('GHC.ShowType Dense) 'GHC.:$$:-       ('GHC.Text "value and use the ") 'GHC.:<>:-       ('GHC.ShowType Fractional) 'GHC.:<>:-       ('GHC.Text " features on it instead."))-  , GoodScale scale-  ) => Fractional (Discrete' currency scale) where-  fromRational = undefined-  recip = undefined-#endif---- | Convert currency 'Discrete' monetary value into a 'Dense' monetary--- value.-fromDiscrete-  :: GoodScale scale-  => Discrete' currency scale-  -> Dense currency -- ^-fromDiscrete = \c@(Discrete i) -> Dense (fromInteger i / scale c)-{-# INLINABLE fromDiscrete #-}---- | Internal. Used to implement 'round', 'ceiling', 'floor' and 'truncate'.-roundf-  :: forall currency scale-  .  GoodScale scale-  => (Rational -> Integer) -- ^ 'Prelude.round', 'Prelude.ceiling' or similar.-  -> Dense currency-  -> (Discrete' currency scale, Dense currency)-roundf f = \c0 ->-  let !r0 = toRational c0 :: Rational-      !r1 = scale (Proxy :: Proxy scale)-      !i2 = f (r0 * r1) :: Integer-      !r2 = fromInteger i2 / r1 :: Rational-      !d2 = Discrete i2-      !rest = Dense (r0 - r2)-  in (d2, rest)-{-# INLINABLE roundf #-}---- | Round a 'Dense' value @x@ to the nearest value fully representable in--- its @currency@'s @unit@ 'Scale', which might be @x@ itself.------ If @x@ is already fully representable in its @currency@'s @unit@ 'Scale',--- then the following holds:------ @--- 'round' x == ('fromDiscrete' x, 0)--- @------ Otherwise, if the nearest value to @x@ that is fully representable in its--- @currency@'s @unit@ 'Scale' is greater than @x@, then the following holds:------ @--- 'round' == 'ceiling'--- @------ Otherwise, the nearest value to @x@ that is fully representable in its--- @currency@'s @unit@ 'Scale' is smaller than @x@, and the following holds:------ @--- 'round' == 'floor'--- @------ Proof that 'round' doesn't lose money:------ @--- x == case 'round' x of (y, z) -> 'fromDiscrete' y + z--- @-round-  :: GoodScale scale-  => Dense currency-  -> (Discrete' currency scale, Dense currency) -- ^-round = roundf Prelude.round-{-# INLINABLE round #-}---- | Round a 'Dense' value @x@ to the nearest value fully representable in--- its @currency@'s @unit@ 'Scale' which is greater than @x@ or equal to @x@.--------- If @x@ is already fully representable in its @currency@'s @unit@ 'Scale',--- then the following holds:------ @--- 'ceiling' x == ('fromDiscrete' x, 0)--- @------ Otherwise, if @x@ is not representable in its @currency@'s @unit@ 'Scale',--- then the following holds:------ @--- 'ceiling' x == (y, z)--- @------ @--- x /= y--- @------ @--- z < 0--- @------ Proof that 'ceiling' doesn't lose money:------ @--- x == case 'ceiling' x of (y, z) -> 'fromDiscrete' y + z--- @-ceiling-  :: GoodScale scale-  => Dense currency-  -> (Discrete' currency scale, Dense currency) -- ^-ceiling = roundf Prelude.ceiling-{-# INLINABLE ceiling #-}---- | Round a 'Dense' value @x@ to the nearest value fully representable in--- its @currency@'s @unit@ 'Scale' which is smaller than @x@ or equal to @x@.--------- If @x@ is already fully representable in its @currency@'s @unit@ 'Scale',--- then the following holds:------ @--- 'floor' x == ('fromDiscrete' x, 0)--- @------ Otherwise, if @x@ is not representable in its @currency@'s @unit@ 'Scale',--- then the following holds:------ @--- 'floor' x == (y, z)--- @------ @--- x /= y--- @------ @--- z > 0--- @------ Proof that 'floor' doesn't lose money:------ @--- x == case 'floor' x of (y, z) -> 'fromDiscrete' y + z--- @-floor-  :: GoodScale scale-  => Dense currency-  -> (Discrete' currency scale, Dense currency) -- ^-floor = roundf Prelude.floor-{-# INLINABLE floor #-}---- | Round a 'Dense' value @x@ to the nearest value between zero and--- @x@ (inclusive) which is fully representable in its @currency@'s @unit@--- 'Scale'.------ If @x@ is already fully representable in its @currency@'s @unit@ 'Scale',--- then the following holds:------ @--- 'truncate' x == ('fromDiscrete' x, 0)--- @------ Otherwise, if @x@ is positive, then the following holds:------ @--- 'truncate' == 'floor'--- @------ Otherwise, if @x@ is negative, the following holds:------ @--- 'truncate' == 'ceiling'--- @------ Proof that 'truncate' doesn't lose money:------ @--- x == case 'truncate' x of (y, z) -> 'fromDiscrete' y + z--- @-truncate-  :: GoodScale scale-  => Dense currency-  -> (Discrete' currency scale, Dense currency) -- ^-truncate = roundf Prelude.truncate-{-# INLINABLE truncate #-}-------------------------------------------------------------------------------------- | @'Scale' currency unit@ is a rational number (expressed as @'(numerator,--- denominator)@) indicating how many pieces of @unit@ fit in @currency@.------ @currency@ is usually a ISO-4217 currency code, but not necessarily.------ The 'Scale' will determine how to convert a 'Dense' value into a--- 'Discrete' value and vice-versa.------ For example, there are 100 USD cents in 1 USD, so the scale for this--- relationship is:------ @--- type instance 'Scale' \"USD\" \"cent\" = '(100, 1)--- @------ As another example, there is 1 dollar in USD, so the scale for this--- relationship is:------ @--- type instance 'Scale' \"USD\" \"dollar\" = '(1, 1)--- @------ When using 'Discrete' values to represent money, it will be impossible to--- represent an amount of @currency@ smaller than @unit@. So, if you decide to--- use @Scale \"USD\" \"dollar\"@ as your scale, you will not be able to--- represent values such as USD 3.50 or USD 21.87, since they are not exact--- multiples of a dollar.------ If there exists a canonical smallest @unit@ that can fully represent the--- currency, then an instance @'Scale' currency currency@ exists.------ @--- type instance 'Scale' \"USD\" \"USD\" = Scale \"USD\" \"cent\"--- @------ For some monetary values, such as precious metals, the smallest representable--- unit is not obvious, since you can continue to split the precious metal many--- times before it stops being a precious metal. Still, for practical purposes--- we can make a sane arbitrary choice of smallest unit. For example, the base--- unit for XAU (Gold) is the /troy ounce/, which is too big to be considered--- the smallest unit, but we can arbitrarily choose the /milligrain/ as our--- smallest unit, which is about as heavy as a single grain of table salt and--- should be sufficiently precise for all monetary practical purposes. A /troy--- ounce/ equals 480000 /milligrains/.------ @--- type instance 'Scale' \"XAG\" \"milligrain\" = '(480000, 1)--- @------ You can use other units such as /milligrams/ for measuring XAU, for example.--- However, since the amount of /milligrams/ in a /troy ounce/ (31103.477) is--- not integral, we need to use rational number to express it.------ @--- type instance 'Scale' \"XAU\" \"milligram\" = '(31103477, 1000)--- @------ If you try to obtain the 'Scale of a @currency@ without an obvious smallest--- representable @unit@, like XAU, you will get a compile error.-type family Scale (currency :: Symbol) (unit :: Symbol) :: (Nat, Nat)--#if MIN_VERSION_base(4,9,0)--- | A friendly 'GHC.TypeError' to use for a @currency@ that doesn't have a--- canonical small unit.-type family ErrScaleNonCanonical (currency :: Symbol) :: k where-  ErrScaleNonCanonical c = GHC.TypeError-    ( 'GHC.Text c 'GHC.:<>:-      'GHC.Text " is not a currency with a canonical smallest unit," 'GHC.:$$:-      'GHC.Text "be explicit about the currency unit you want to use." )-#else--- | Forbid a @currency@ that doesn't have a canonical small unit.------ In GHC versions before 8.0 we can't provide a nice error message here, so we--- simply set this to a value that will fail to satisfy 'GoodScale'. As a--- consequence, trying to use this 'Scale' will result in a cryptic error saying--- /«@Couldn't match type ‘'EQ’ with ‘'LT’@»/.-type ErrScaleNonCanonical (currency :: Symbol) = '(0, 0)-#endif---- | Constraints to a scale (like the one returned by @'Scale' currency unit@)--- expected to always be satisfied. In particular, the scale is always--- guaranteed to be a positive rational number ('infinity' and 'notANumber' are--- forbidden by 'GoodScale').-type GoodScale (scale :: (Nat, Nat))-   = ( CmpNat 0 (Fst scale) ~ 'LT-     , CmpNat 0 (Snd scale) ~ 'LT-     , KnownNat (Fst scale)-     , KnownNat (Snd scale)-     )---- | If the specified @num@ and @den@ satisfy the expectations of 'GoodScale' at--- the type level, then construct a proof for 'GoodScale'.-mkGoodScale-  :: forall num den-  .  (KnownNat num, KnownNat den)-  => Maybe (Dict (GoodScale '(num, den)))-mkGoodScale =-  let n = natVal (Proxy :: Proxy num)-      d = natVal (Proxy :: Proxy den)-  in if (n > 0) && (d > 0)-     then Just (unsafeCoerce (Dict :: Dict ('LT ~ 'LT, 'LT ~ 'LT,-                                            KnownNat num, KnownNat den)))-     else Nothing-{-# INLINABLE mkGoodScale #-}---- | Term-level representation for the @currency@'s @unit@ 'Scale'.------ For example, the 'Scale' for @\"USD\"@ in @\"cent\"@s is @100/1@.------ The returned 'Rational' is statically guaranteed to be a positive number, and--- to be different from both 'notANumber' and 'infinity'.-scale :: forall proxy scale. GoodScale scale => proxy scale -> Rational -- ^-scale = \_ ->-   natVal (Proxy :: Proxy (Fst scale)) %-   natVal (Proxy :: Proxy (Snd scale))-{-# INLINABLE scale #-}-------------------------------------------------------------------------------------- | Exchange rate for converting monetary values of currency @src@ into--- monetary values of currency @dst@ by multiplying for it.------ For example, if in order to convert USD to GBP we have to multiply by 1.2345,--- then we can represent this situaion using:------ @--- 'exchangeRate' (12345 % 10000) :: 'Maybe' ('ExchangeRate' \"USD\" \"GBP\")--- @-newtype ExchangeRate (src :: Symbol) (dst :: Symbol) = ExchangeRate Rational-  deriving (Eq, Ord, GHC.Generic)----- | Composition of 'ExchangeRate's multiplies exchange rates together:------ @--- 'fromExchangeRate' x * 'fromExchangeRate' y  ==  'fromExchangeRate' (x . y)--- @------ Identity:------ @--- x  ==  x . id  ==  id . x--- @------ Associativity:------ @--- x . y . z  ==  x . (y . z)  ==  (x . y) . z--- @------ Conmutativity (provided the types allow for composition):------ @--- x . y  ==  y . x--- @------ Multiplicative inverse:------ @--- 1  ==  'fromExchangeRate' (x . 'flipExchangeRate' x)--- @-instance Category ExchangeRate where-  id = ExchangeRate 1-  {-# INLINE id #-}-  ExchangeRate a . ExchangeRate b = ExchangeRate (a * b)-  {-# INLINE (.) #-}--instance forall src dst.-  ( KnownSymbol src, KnownSymbol dst-  ) => Show (ExchangeRate src dst) where-  showsPrec n = \(ExchangeRate r0) ->-    let s = symbolVal (Proxy :: Proxy src)-        d = symbolVal (Proxy :: Proxy dst)-    in showParen (n > 10) $-         showString "ExchangeRate " . showsPrec 0 s . showChar ' ' .-         showsPrec 0 d . showChar ' ' .-         showsPrec 0 (numerator r0) . showChar '%' .-         showsPrec 0 (denominator r0)--instance forall src dst.-  ( KnownSymbol src, KnownSymbol dst-  ) => Read (ExchangeRate (src :: Symbol) (dst :: Symbol)) where-  readPrec = Read.parens $ do-    let s = symbolVal (Proxy :: Proxy src)-        d = symbolVal (Proxy :: Proxy dst)-    _ <- ReadPrec.lift (ReadP.string-            ("ExchangeRate " ++ show s ++ " " ++ show d ++ " "))-    maybe empty pure =<< fmap exchangeRate Read.readPrec---- | Obtain a 'Rational' representation of the 'ExchangeRate'.------ This 'Rational' is statically guaranteed to be greater than 0, different--- from 'infinity' and different from 'notANumber'.-fromExchangeRate :: ExchangeRate src dst -> Rational-fromExchangeRate = \(ExchangeRate r0) -> r0-{-# INLINABLE fromExchangeRate #-}---- | Safely construct an 'ExchangeRate' from a 'Rational' number.------ For construction to succeed, this 'Rational' must be greater than 0,--- different from 'infinity' and different from 'notANumber'.-exchangeRate :: Rational -> Maybe (ExchangeRate src dst)-exchangeRate = \r0 ->-  if (r0 <= 0 || infinity == r0 || notANumber == r0)-  then Nothing else Just (ExchangeRate r0)-{-# INLINABLE exchangeRate #-}---- | Flip the direction of an 'ExchangeRate'.------ This function retuns the multiplicative inverse of the given 'ExchangeRate',--- leading to the following identity law:------ @--- 'flipExchangeRate' . 'flipExchangeRate'   ==  'id'--- @-flipExchangeRate :: ExchangeRate a b -> ExchangeRate b a-flipExchangeRate = \(ExchangeRate x) -> ExchangeRate (1 / x)-{-# INLINABLE flipExchangeRate #-}---- | Apply the 'ExchangeRate' to the given @'Dense' src@ monetary value.------ Identity law:------ @--- 'exchange' ('flipExchangeRate' x) . 'exchange' x  ==  'id'--- @------ Use the /Identity law/ for reasoning about going back and forth between @src@--- and @dst@ in order to manage any leftovers that might not be representable as--- a 'Discrete' monetary value of @src@.-exchange :: ExchangeRate src dst -> Dense src -> Dense dst-exchange = \(ExchangeRate r) -> \(Dense s) -> Dense (r * s)-{-# INLINABLE exchange #-}------------------------------------------------------------------------------------- SomeDense---- | A monomorphic representation of 'Dense' that is easier to serialize and--- deserialize than 'Dense' in case you don't know the type indexes involved.------ If you are trying to construct a value of this type from some raw input, then--- you will need to use the 'mkSomeDense' function.------ In order to be able to effectively serialize a 'SomeDense' value, you--- need to serialize the following three values (which are the eventual--- arguments to 'mkSomeDense'):------ * 'someDenseCurrency'--- * 'someDenseAmount'-data SomeDense = SomeDense-  { _someDenseCurrency          :: !String-  , _someDenseAmount            :: !Rational-  } deriving (Eq, Show, GHC.Generic)---- | WARNING: This instance does not compare monetary amounts, it just helps you--- sort 'SomeDense' values in case you need to put them in a 'Data.Set.Set' or--- similar.-deriving instance Ord SomeDense---- | Currency name.-someDenseCurrency :: SomeDense -> String-someDenseCurrency = _someDenseCurrency-{-# INLINABLE someDenseCurrency #-}---- | Currency unit amount.-someDenseAmount :: SomeDense -> Rational-someDenseAmount = _someDenseAmount-{-# INLINABLE someDenseAmount #-}---- | Build a 'SomeDense' from raw values.------ This function is intended for deserialization purposes. You need to convert--- this 'SomeDense' value to a 'Dense' value in order to do any arithmetic--- operation on the monetary value.-mkSomeDense-  :: String   -- ^ Currency. ('someDenseCurrency')-  -> Rational -- ^ Scale. ('someDenseAmount')-  -> Maybe SomeDense-mkSomeDense = \c r -> do-  guard (denominator r /= 0)-  Just (SomeDense c r)-{-# INLINABLE mkSomeDense #-}---- | Convert a 'Dense' to a 'SomeDense' for ease of serialization.-toSomeDense :: KnownSymbol currency => Dense currency -> SomeDense-toSomeDense = \(Dense r0 :: Dense currency) ->-  let c = symbolVal (Proxy :: Proxy currency)-  in SomeDense c r0-{-# INLINABLE toSomeDense #-}---- | Attempt to convert a 'SomeDense' to a 'Dense', provided you know the target--- @currency@.-fromSomeDense-  :: forall currency-  .  KnownSymbol currency-  => SomeDense-  -> Maybe (Dense currency)  -- ^-fromSomeDense = \dr -> do-  guard (someDenseCurrency dr == symbolVal (Proxy :: Proxy currency))-  Just (Dense (someDenseAmount dr))-{-# INLINABLE fromSomeDense #-}---- | Convert a 'SomeDense' to a 'Dense' without knowing the target @currency@.------ Notice that @currency@ here can't leave its intended scope unless you can--- prove equality with some other type at the outer scope, but in that case you--- would be better off using 'fromSomeDense' directly.-withSomeDense-  :: SomeDense-  -> (forall currency. KnownSymbol currency => Dense currency -> r)-  -> r  -- ^-withSomeDense dr = \f ->-   case someSymbolVal (someDenseCurrency dr) of-      SomeSymbol (Proxy :: Proxy currency) ->-         f (Dense (someDenseAmount dr) :: Dense currency)-{-# INLINABLE withSomeDense #-}------------------------------------------------------------------------------------- SomeDiscrete---- | A monomorphic representation of 'Discrete' that is easier to serialize and--- deserialize than 'Discrete' in case you don't know the type indexes involved.------ If you are trying to construct a value of this type from some raw input, then--- you will need to use the 'mkSomeDiscrete' function.------ In order to be able to effectively serialize a 'SomeDiscrete' value, you need--- to serialize the following four values (which are the eventual arguments to--- 'mkSomeDiscrete'):------ * 'someDiscreteCurrency'--- * 'someDiscreteScale'--- * 'someDiscreteAmount'-data SomeDiscrete = SomeDiscrete-  { _someDiscreteCurrency :: !String   -- ^ Currency name.-  , _someDiscreteScale    :: !Rational -- ^ Positive, non-zero.-  , _someDiscreteAmount   :: !Integer  -- ^ Amount of unit.-  } deriving (Eq, Show, GHC.Generic)---- | WARNING: This instance does not compare monetary amounts, it just helps you--- sort 'SomeDiscrete' values in case you need to put them in a 'Data.Set.Set' or--- similar.-deriving instance Ord SomeDiscrete---- | Currency name.-someDiscreteCurrency :: SomeDiscrete -> String-someDiscreteCurrency = _someDiscreteCurrency-{-# INLINABLE someDiscreteCurrency #-}---- | Positive, non-zero.-someDiscreteScale :: SomeDiscrete -> Rational-someDiscreteScale = _someDiscreteScale-{-# INLINABLE someDiscreteScale #-}---- | Amount of currency unit.-someDiscreteAmount :: SomeDiscrete -> Integer-someDiscreteAmount = _someDiscreteAmount-{-# INLINABLE someDiscreteAmount #-}---- | Internal. Build a 'SomeDiscrete' from raw values.------ This function is intended for deserialization purposes. You need to convert--- this 'SomeDiscrete' value to a 'Discrete' vallue in order to do any arithmetic--- operation on the monetary value.-mkSomeDiscrete-  :: String   -- ^ Currency name. ('someDiscreteCurrency')-  -> Rational -- ^ Scale. Positive, non-zero. ('someDiscreteScale')-  -> Integer  -- ^ Amount of unit. ('someDiscreteAmount')-  -> Maybe SomeDiscrete-mkSomeDiscrete = \c r a -> do-  guard (denominator r /= 0)-  guard (r > 0)-  Just (SomeDiscrete c r a)-{-# INLINABLE mkSomeDiscrete #-}---- | Convert a 'Discrete' to a 'SomeDiscrete' for ease of serialization.-toSomeDiscrete-  :: (KnownSymbol currency, GoodScale scale)-  => Discrete' currency scale-  -> SomeDiscrete -- ^-toSomeDiscrete = \(Discrete i0 :: Discrete' currency scale) ->-  let c = symbolVal (Proxy :: Proxy currency)-      n = natVal (Proxy :: Proxy (Fst scale))-      d = natVal (Proxy :: Proxy (Snd scale))-  in SomeDiscrete c (n % d) i0-{-# INLINABLE toSomeDiscrete #-}---- | Attempt to convert a 'SomeDiscrete' to a 'Discrete', provided you know the--- target @currency@ and @unit@.-fromSomeDiscrete-  :: forall currency scale-  .  (KnownSymbol currency, GoodScale scale)-  => SomeDiscrete-  -> Maybe (Discrete' currency scale)  -- ^-fromSomeDiscrete = \dr ->-   if (someDiscreteCurrency dr == symbolVal (Proxy :: Proxy currency)) &&-      (someDiscreteScale dr == scale (Proxy :: Proxy scale))-   then Just (Discrete (someDiscreteAmount dr))-   else Nothing-{-# INLINABLE fromSomeDiscrete #-}---- | Convert a 'SomeDiscrete' to a 'Discrete' without knowing the target--- @currency@ and @unit@.------ Notice that @currency@ and @unit@ here can't leave its intended scope unless--- you can prove equality with some other type at the outer scope, but in that--- case you would be better off using 'fromSomeDiscrete' directly.------ Notice that you may need to add an explicit type to the result of this--- function in order to keep the compiler happy.-withSomeDiscrete-  :: forall r-  .  SomeDiscrete-  -> ( forall currency scale.-         ( KnownSymbol currency-         , GoodScale scale-         ) => Discrete' currency scale-           -> r )-  -> r  -- ^-withSomeDiscrete dr = \f ->-  case someSymbolVal (someDiscreteCurrency dr) of-    SomeSymbol (Proxy :: Proxy currency) ->-      case someNatVal (numerator (someDiscreteScale dr)) of-        Nothing -> error "withSomeDiscrete: impossible: numerator < 0"-        Just (SomeNat (Proxy :: Proxy num)) ->-          case someNatVal (denominator (someDiscreteScale dr)) of-            Nothing -> error "withSomeDiscrete: impossible: denominator < 0"-            Just (SomeNat (Proxy :: Proxy den)) ->-              case mkGoodScale of-                Nothing -> error "withSomeDiscrete: impossible: mkGoodScale"-                Just (Dict :: Dict (GoodScale '(num, den))) ->-                  f (Discrete (someDiscreteAmount dr)-                       :: Discrete' currency '(num, den))-{-# INLINABLE withSomeDiscrete #-}------------------------------------------------------------------------------------- SomeExchangeRate---- | A monomorphic representation of 'ExchangeRate' that is easier to serialize--- and deserialize than 'ExchangeRate' in case you don't know the type indexes--- involved.------ If you are trying to construct a value of this type from some raw input, then--- you will need to use the 'mkSomeExchangeRate' function.------ In order to be able to effectively serialize an 'SomeExchangeRate' value, you--- need to serialize the following four values (which are the eventual arguments--- to 'mkSomeExchangeRate'):------ * 'someExchangeRateSrcCurrency'--- * 'someExchangeRateDstCurrency'--- * 'someExchangeRateRate'-data SomeExchangeRate = SomeExchangeRate-  { _someExchangeRateSrcCurrency     :: !String-  , _someExchangeRateDstCurrency     :: !String-  , _someExchangeRateRate            :: !Rational -- ^ Positive, non-zero.-  } deriving (Eq, Show, GHC.Generic)---- | WARNING: This instance does not compare monetary amounts, it just helps you--- sort 'SomeExchangeRate' values in case you need to put them in a--- 'Data.Set.Set' or similar.-deriving instance Ord SomeExchangeRate---- | Source currency name.-someExchangeRateSrcCurrency :: SomeExchangeRate -> String-someExchangeRateSrcCurrency = _someExchangeRateSrcCurrency-{-# INLINABLE someExchangeRateSrcCurrency #-}---- | Destination currency name.-someExchangeRateDstCurrency :: SomeExchangeRate -> String-someExchangeRateDstCurrency = _someExchangeRateDstCurrency-{-# INLINABLE someExchangeRateDstCurrency #-}---- | Exchange rate. Positive, non-zero.-someExchangeRateRate :: SomeExchangeRate -> Rational-someExchangeRateRate = _someExchangeRateRate-{-# INLINABLE someExchangeRateRate #-}---- | Internal. Build a 'SomeExchangeRate' from raw values.------ This function is intended for deserialization purposes. You need to convert--- this 'SomeExchangeRate' value to a 'ExchangeRate' value in order to do any--- arithmetic operation with the exchange rate.-mkSomeExchangeRate-  :: String   -- ^ Source currency name. ('someExchangeRateSrcCurrency')-  -> String   -- ^ Destination currency name. ('someExchangeRateDstCurrency')-  -> Rational -- ^ Exchange rate . Positive, non-zero. ('someExchangeRateRate')-  -> Maybe SomeExchangeRate-mkSomeExchangeRate = \src dst r -> do-  guard (denominator r /= 0)-  guard (r > 0)-  Just (SomeExchangeRate src dst r)-{-# INLINABLE mkSomeExchangeRate #-}---- | Convert a 'ExchangeRate' to a 'SomeDiscrete' for ease of serialization.-toSomeExchangeRate-  :: (KnownSymbol src, KnownSymbol dst)-  => ExchangeRate src dst-  -> SomeExchangeRate -- ^-toSomeExchangeRate = \(ExchangeRate r0 :: ExchangeRate src dst) ->-  let src = symbolVal (Proxy :: Proxy src)-      dst = symbolVal (Proxy :: Proxy dst)-  in SomeExchangeRate src dst r0-{-# INLINABLE toSomeExchangeRate #-}---- | Attempt to convert a 'SomeExchangeRate' to a 'ExchangeRate', provided you--- know the target @src@ and @dst@ types.-fromSomeExchangeRate-  :: forall src dst-  .  (KnownSymbol src, KnownSymbol dst)-  => SomeExchangeRate-  -> Maybe (ExchangeRate src dst)  -- ^-fromSomeExchangeRate = \x ->-   if (someExchangeRateSrcCurrency x == symbolVal (Proxy :: Proxy src)) &&-      (someExchangeRateDstCurrency x == symbolVal (Proxy :: Proxy dst))-   then Just (ExchangeRate (someExchangeRateRate x))-   else Nothing-{-# INLINABLE fromSomeExchangeRate #-}---- | Convert a 'SomeExchangeRate' to a 'ExchangeRate' without knowing the target--- @currency@ and @unit@.------ Notice that @src@ and @dst@ here can't leave its intended scope unless--- you can prove equality with some other type at the outer scope, but in that--- case you would be better off using 'fromSomeExchangeRate' directly.-withSomeExchangeRate-  :: SomeExchangeRate-  -> ( forall src dst.-         ( KnownSymbol src-         , KnownSymbol dst-         ) => ExchangeRate src dst-           -> r )-  -> r  -- ^-withSomeExchangeRate x = \f ->-  case someSymbolVal (someExchangeRateSrcCurrency x) of-    SomeSymbol (Proxy :: Proxy src) ->-      case someSymbolVal (someExchangeRateDstCurrency x) of-        SomeSymbol (Proxy :: Proxy dst) ->-          f (ExchangeRate (someExchangeRateRate x) :: ExchangeRate src dst)-{-# INLINABLE withSomeExchangeRate #-}------------------------------------------------------------------------------------- Miscellaneous--type family Fst (ab :: (ka, kb)) :: ka where Fst '(a,b) = a-type family Snd (ab :: (ka, kb)) :: ka where Snd '(a,b) = b------------------------------------------------------------------------------------- Extra instances: hashable-#ifdef HAS_hashable-instance Hashable (Dense currency)-instance Hashable SomeDense-instance GoodScale scale => Hashable (Discrete' currency scale)-instance Hashable SomeDiscrete-instance Hashable (ExchangeRate src dst)-instance Hashable SomeExchangeRate-#endif------------------------------------------------------------------------------------- Extra instances: deepseq-#ifdef HAS_deepseq-instance NFData (Dense currency)-instance NFData SomeDense-instance GoodScale scale => NFData (Discrete' currency scale)-instance NFData SomeDiscrete-instance NFData (ExchangeRate src dst)-instance NFData SomeExchangeRate-#endif------------------------------------------------------------------------------------- Extra instances: cereal-#ifdef HAS_cereal--- | Compatible with 'SomeDense'.-instance (KnownSymbol currency) => Cereal.Serialize (Dense currency) where-  put = Cereal.put . toSomeDense-  get = maybe empty pure =<< fmap fromSomeDense Cereal.get---- | Compatible with 'SomeDiscrete'.-instance-  ( KnownSymbol currency, GoodScale scale-  ) => Cereal.Serialize (Discrete' currency scale) where-  put = Cereal.put . toSomeDiscrete-  get = maybe empty pure =<< fmap fromSomeDiscrete Cereal.get---- | Compatible with 'SomeExchangeRate'.-instance-  ( KnownSymbol src, KnownSymbol dst-  ) => Cereal.Serialize (ExchangeRate src dst) where-  put = Cereal.put . toSomeExchangeRate-  get = maybe empty pure =<< fmap fromSomeExchangeRate Cereal.get---- | Compatible with 'Dense'.-instance Cereal.Serialize SomeDense where-  put = \(SomeDense c r) -> do-    Cereal.put c-    Cereal.put (numerator r)-    Cereal.put (denominator r)-  get = maybe empty pure =<< do-    c :: String <- Cereal.get-    n :: Integer <- Cereal.get-    d :: Integer <- Cereal.get-    when (d == 0) (fail "denominator is zero")-    pure (mkSomeDense c (n % d))---- | Compatible with 'Discrete'.-instance Cereal.Serialize SomeDiscrete where-  put = \(SomeDiscrete c r a) -> do-    Cereal.put c-    Cereal.put (numerator r)-    Cereal.put (denominator r)-    Cereal.put a-  get = maybe empty pure =<< do-    c :: String <- Cereal.get-    n :: Integer <- Cereal.get-    d :: Integer <- Cereal.get-    when (d == 0) (fail "denominator is zero")-    a :: Integer <- Cereal.get-    pure (mkSomeDiscrete c (n % d) a)---- | Compatible with 'ExchangeRate'.-instance Cereal.Serialize SomeExchangeRate where-  put = \(SomeExchangeRate src dst r) -> do-    Cereal.put src-    Cereal.put dst-    Cereal.put (numerator r)-    Cereal.put (denominator r)-  get = maybe empty pure =<< do-    src :: String <- Cereal.get-    dst :: String <- Cereal.get-    n :: Integer <- Cereal.get-    d :: Integer <- Cereal.get-    when (d == 0) (fail "denominator is zero")-    pure (mkSomeExchangeRate src dst (n % d))-#endif----------------------------------------------------------------------------------- Extra instances: binary-#ifdef HAS_binary--- | Compatible with 'SomeDense'.-instance (KnownSymbol currency) => Binary.Binary (Dense currency) where-  put = Binary.put . toSomeDense-  get = maybe empty pure =<< fmap fromSomeDense Binary.get---- | Compatible with 'SomeDiscrete'.-instance-  ( KnownSymbol currency, GoodScale scale-  ) => Binary.Binary (Discrete' currency scale) where-  put = Binary.put . toSomeDiscrete-  get = maybe empty pure =<< fmap fromSomeDiscrete Binary.get---- | Compatible with 'SomeExchangeRate'.-instance-  ( KnownSymbol src, KnownSymbol dst-  ) => Binary.Binary (ExchangeRate src dst) where-  put = Binary.put . toSomeExchangeRate-  get = maybe empty pure =<< fmap fromSomeExchangeRate Binary.get---- | Compatible with 'Dense'.-instance Binary.Binary SomeDense where-  put = \(SomeDense c r) ->-    Binary.put c >> Binary.put (numerator r) >> Binary.put (denominator r)-  get = maybe empty pure =<< do-    c :: String <- Binary.get-    n :: Integer <- Binary.get-    d :: Integer <- Binary.get-    when (d == 0) (fail "denominator is zero")-    pure (mkSomeDense c (n % d))---- | Compatible with 'Discrete'.-instance Binary.Binary SomeDiscrete where-  put = \(SomeDiscrete c r a) ->-    Binary.put c <>-    Binary.put (numerator r) <>-    Binary.put (denominator r) <>-    Binary.put a-  get = maybe empty pure =<< do-    c :: String <- Binary.get-    n :: Integer <- Binary.get-    d :: Integer <- Binary.get-    when (d == 0) (fail "denominator is zero")-    a :: Integer <- Binary.get-    pure (mkSomeDiscrete c (n % d) a)---- | Compatible with 'ExchangeRate'.-instance Binary.Binary SomeExchangeRate where-  put = \(SomeExchangeRate src dst r) -> do-    Binary.put src-    Binary.put dst-    Binary.put (numerator r)-    Binary.put (denominator r)-  get = maybe empty pure =<< do-    src :: String <- Binary.get-    dst :: String <- Binary.get-    n :: Integer <- Binary.get-    d :: Integer <- Binary.get-    when (d == 0) (fail "denominator is zero")-    pure (mkSomeExchangeRate src dst (n % d))-#endif------------------------------------------------------------------------------------- Extra instances: serialise-#ifdef HAS_serialise--- | Compatible with 'SomeDense'.-instance (KnownSymbol currency) => Ser.Serialise (Dense currency) where-  encode = Ser.encode . toSomeDense-  decode = maybe (fail "Dense") pure =<< fmap fromSomeDense Ser.decode---- | Compatible with 'SomeDiscrete'.-instance-  ( KnownSymbol currency, GoodScale scale-  ) => Ser.Serialise (Discrete' currency scale) where-  encode = Ser.encode . toSomeDiscrete-  decode = maybe (fail "Discrete'") pure =<< fmap fromSomeDiscrete Ser.decode---- | Compatible with 'SomeExchangeRate'.-instance-  ( KnownSymbol src, KnownSymbol dst-  ) => Ser.Serialise (ExchangeRate src dst) where-  encode = Ser.encode . toSomeExchangeRate-  decode = maybe (fail "ExchangeRate") pure-             =<< fmap fromSomeExchangeRate Ser.decode---- | Compatible with 'Dense'.-instance Ser.Serialise SomeDense where-  encode = \(SomeDense c r) ->-    Ser.encode c <> Ser.encode (numerator r) <> Ser.encode (denominator r)-  decode = maybe (fail "SomeDense") pure =<< do-    c :: String <- Ser.decode-    n :: Integer <- Ser.decode-    d :: Integer <- Ser.decode-    when (d == 0) (fail "denominator is zero")-    pure (mkSomeDense c (n % d))---- | Compatible with 'Discrete'.-instance Ser.Serialise SomeDiscrete where-  encode = \(SomeDiscrete c r a) ->-    Ser.encode c <>-    Ser.encode (numerator r) <>-    Ser.encode (denominator r) <>-    Ser.encode a-  decode = maybe (fail "SomeDiscrete") pure =<< do-    c :: String <- Ser.decode-    n :: Integer <- Ser.decode-    d :: Integer <- Ser.decode-    when (d == 0) (fail "denominator is zero")-    a :: Integer <- Ser.decode-    pure (mkSomeDiscrete c (n % d) a)---- | Compatible with 'ExchangeRate'.-instance Ser.Serialise SomeExchangeRate where-  encode = \(SomeExchangeRate src dst r) ->-    Ser.encode src <>-    Ser.encode dst <>-    Ser.encode (numerator r) <>-    Ser.encode (denominator r)-  decode = maybe (fail "SomeExchangeRate") pure =<< do-    src :: String <- Ser.decode-    dst :: String <- Ser.decode-    n :: Integer <- Ser.decode-    d :: Integer <- Ser.decode-    when (d == 0) (fail "denominator is zero")-    pure (mkSomeExchangeRate src dst (n % d))-#endif------------------------------------------------------------------------------------- Extra instances: aeson-#ifdef HAS_aeson--- | Compatible with 'SomeDense'------ Example rendering @'fromRational' (2 % 3) :: 'Dense' \"BTC\"@:------ @--- [\"BTC\", 2, 3]--- @------ Note: The JSON serialization changed in version 0.4 (the leading @"Dense"@--- string was dropped from the rendered 'Ae.Array').-instance KnownSymbol currency => Ae.ToJSON (Dense currency) where-  toJSON = Ae.toJSON . toSomeDense---- | Compatible with 'SomeDense'------ Note: The JSON serialization changed in version 0.4. However, this instance--- is still able to cope with the previous format.-instance KnownSymbol currency => Ae.FromJSON (Dense currency) where-  parseJSON = maybe empty pure <=< fmap fromSomeDense . Ae.parseJSON---- | Compatible with 'Dense'------ Note: The JSON serialization changed in version 0.4 (the leading @"Dense"@--- string was dropped from the rendered 'Ae.Array').-instance Ae.ToJSON SomeDense where-  toJSON = \(SomeDense c r) ->-    Ae.toJSON (c, numerator r, denominator r)---- | Compatible with 'Dense'.------ Note: The JSON serialization changed in version 0.4. However, this instance--- is still able to cope with the previous format.-instance Ae.FromJSON SomeDense where-  parseJSON = \v -> do-    (c, n, d) <- Ae.parseJSON v <|> do-       -- Pre 0.4 format.-       ("Dense" :: String, c, n, d) <- Ae.parseJSON v-       pure (c, n, d)-    when (d == 0) (fail "denominator is zero")-    maybe empty pure (mkSomeDense c (n % d))---- | Compatible with 'SomeDiscrete'------ Example rendering @43 :: 'Discrete' \"BTC\" \"satoshi\"@:------ @--- [\"BTC\", 100000000, 1, 43]--- @------ Note: The JSON serialization changed in version 0.4 (the leading @"Discrete"@--- string was dropped from the rendered 'Ae.Array').-instance-  ( KnownSymbol currency, GoodScale scale-  ) => Ae.ToJSON (Discrete' currency scale) where-  toJSON = Ae.toJSON . toSomeDiscrete---- | Compatible with 'SomeDiscrete'------ Note: The JSON serialization changed in version 0.4. However, this instance--- is still able to cope with the previous format.-instance-  ( KnownSymbol currency, GoodScale scale-  ) => Ae.FromJSON (Discrete' currency scale) where-  parseJSON = maybe empty pure <=< fmap fromSomeDiscrete . Ae.parseJSON---- | Compatible with 'Discrete''------ Note: The JSON serialization changed in version 0.4 (the leading @"Discrete"@--- string was dropped from the rendered 'Ae.Array').-instance Ae.ToJSON SomeDiscrete where-  toJSON = \(SomeDiscrete c r a) ->-    Ae.toJSON (c, numerator r, denominator r, a)---- | Compatible with 'Discrete''------ Note: The JSON serialization changed in version 0.4. However, this instance--- is still able to cope with the previous format.-instance Ae.FromJSON SomeDiscrete where-  parseJSON = \v -> do-    (c, n, d, a) <- Ae.parseJSON v <|> do-       -- Pre 0.4 format.-       ("Discrete" :: String, c, n, d, a) <- Ae.parseJSON v-       pure (c, n, d, a)-    when (d == 0) (fail "denominator is zero")-    maybe empty pure (mkSomeDiscrete c (n % d) a)---- | Compatible with 'SomeExchangeRate'------ Example rendering an 'ExchangeRate' constructed with--- @'exchangeRate' (5 % 7) :: 'Maybe' ('ExchangeRate' \"USD\" \"JPY\")@------ @--- [\"USD\", \"JPY\", 5, 7]--- @------ Note: The JSON serialization changed in version 0.4 (the leading--- @"ExchangeRate"@ string was dropped from the rendered 'Ae.Array').-instance-  ( KnownSymbol src, KnownSymbol dst-  ) => Ae.ToJSON (ExchangeRate src dst) where-  toJSON = Ae.toJSON . toSomeExchangeRate---- | Compatible with 'SomeExchangeRate'------ Note: The JSON serialization changed in version 0.4. However, this instance--- is still able to cope with the previous format.-instance-  ( KnownSymbol src, KnownSymbol dst-  ) => Ae.FromJSON (ExchangeRate src dst) where-  parseJSON = maybe empty pure <=< fmap fromSomeExchangeRate . Ae.parseJSON---- | Compatible with 'ExchangeRate'------ Note: The JSON serialization changed in version 0.4 (the leading--- @"ExchangeRate"@ string was dropped from the rendered 'Ae.Array').-instance Ae.ToJSON SomeExchangeRate where-  toJSON = \(SomeExchangeRate src dst r) ->-    Ae.toJSON (src, dst, numerator r, denominator r)---- | Compatible with 'ExchangeRate'------ Note: The JSON serialization changed in version 0.4. However, this instance--- is still able to cope with the previous format.-instance Ae.FromJSON SomeExchangeRate where-  parseJSON = \v -> do-    (src, dst, n, d) <- Ae.parseJSON v <|> do-       -- Pre 0.4 format.-       ("ExchangeRate" :: String, src, dst, n, d) <- Ae.parseJSON v-       pure (src, dst, n, d)-    when (d == 0) (fail "denominator is zero")-    maybe empty pure (mkSomeExchangeRate src dst (n % d))-#endif------------------------------------------------------------------------------------- Extra instances: xmlbf-#ifdef HAS_xmlbf---- | Compatible with 'SomeDense'------ Example rendering @'fromRational' (2 % 3) :: 'Dense' \"BTC\"@:------ @--- \<money-dense c=\"BTC\" n=\"2\" d=\"3\"/>--- @-instance KnownSymbol currency => Xmlbf.ToXml (Dense currency) where-  toXml = Xmlbf.toXml . toSomeDense---- | Compatible with 'SomeDense'-instance KnownSymbol currency => Xmlbf.FromXml (Dense currency) where-  fromXml = maybe empty pure =<< fmap fromSomeDense Xmlbf.fromXml---- | Compatible with 'Dense'-instance Xmlbf.ToXml SomeDense where-  toXml = \(SomeDense c r) ->-    let as = [ (Text.pack "c", Text.pack c)-             , (Text.pack "n", Text.pack (show (numerator r)))-             , (Text.pack "d", Text.pack (show (denominator r))) ]-        Right e = Xmlbf.element (Text.pack "money-dense") (fromList as) []-    in [e]---- | Compatible with 'Dense'.-instance Xmlbf.FromXml SomeDense where-  fromXml = Xmlbf.pElement (Text.pack "money-dense") $ do-    c <- Text.unpack <$> Xmlbf.pAttr "c"-    n <- Xmlbf.pRead =<< Xmlbf.pAttr "n"-    d <- Xmlbf.pRead =<< Xmlbf.pAttr "d"-    when (d == 0) (fail "denominator is zero")-    maybe empty pure (mkSomeDense c (n % d))---- | Compatible with 'SomeDiscrete'------ Example rendering @43 :: 'Discrete' \"BTC\" \"satoshi\"@:------ @--- \<money-discrete c=\"BTC\" n=\"100000000\" d=\"1\" a=\"43\"/>--- @-instance-  ( KnownSymbol currency, GoodScale scale-  ) => Xmlbf.ToXml (Discrete' currency scale) where-  toXml = Xmlbf.toXml . toSomeDiscrete---- | Compatible with 'SomeDiscrete'-instance-  ( KnownSymbol currency, GoodScale scale-  ) => Xmlbf.FromXml (Discrete' currency scale) where-  fromXml = maybe empty pure =<< fmap fromSomeDiscrete Xmlbf.fromXml---- | Compatible with 'Discrete''-instance Xmlbf.ToXml SomeDiscrete where-  toXml = \(SomeDiscrete c r a) ->-    let as = [ (Text.pack "c", Text.pack c)-             , (Text.pack "n", Text.pack (show (numerator r)))-             , (Text.pack "d", Text.pack (show (denominator r)))-             , (Text.pack "a", Text.pack (show a)) ]-        Right e = Xmlbf.element (Text.pack "money-discrete") (fromList as) []-    in [e]---- | Compatible with 'Discrete''-instance Xmlbf.FromXml SomeDiscrete where-  fromXml = Xmlbf.pElement (Text.pack "money-discrete") $ do-    c <- Text.unpack <$> Xmlbf.pAttr "c"-    n <- Xmlbf.pRead =<< Xmlbf.pAttr "n"-    d <- Xmlbf.pRead =<< Xmlbf.pAttr "d"-    when (d == 0) (fail "denominator is zero")-    a <- Xmlbf.pRead =<< Xmlbf.pAttr "a"-    maybe empty pure (mkSomeDiscrete c (n % d) a)---- | Compatible with 'SomeExchangeRate'------ Example rendering an 'ExchangeRate' constructed with--- @'exchangeRate' (5 % 7) :: 'Maybe' ('ExchangeRate' \"USD\" \"JPY\")@------ @--- \<exchange-rate src=\"USD\" dst=\"JPY\" n=\"5\" d=\"7\"/>--- @-instance-  ( KnownSymbol src, KnownSymbol dst-  ) => Xmlbf.ToXml (ExchangeRate src dst) where-  toXml = Xmlbf.toXml . toSomeExchangeRate---- | Compatible with 'SomeExchangeRate'-instance-  ( KnownSymbol src, KnownSymbol dst-  ) => Xmlbf.FromXml (ExchangeRate src dst) where-  fromXml = maybe empty pure =<< fmap fromSomeExchangeRate Xmlbf.fromXml---- | Compatible with 'ExchangeRate'-instance Xmlbf.ToXml SomeExchangeRate where-  toXml = \(SomeExchangeRate src dst r) ->-    let as = [ (Text.pack "src", Text.pack src)-             , (Text.pack "dst", Text.pack dst)-             , (Text.pack "n", Text.pack (show (numerator r)))-             , (Text.pack "d", Text.pack (show (denominator r))) ]-        Right e = Xmlbf.element (Text.pack "exchange-rate") (fromList as) []-    in [e]---- | Compatible with 'ExchangeRate'-instance Xmlbf.FromXml SomeExchangeRate where-  fromXml = Xmlbf.pElement (Text.pack "exchange-rate") $ do-    src <- Text.unpack <$> Xmlbf.pAttr "src"-    dst <- Text.unpack <$> Xmlbf.pAttr "dst"-    n <- Xmlbf.pRead =<< Xmlbf.pAttr "n"-    d <- Xmlbf.pRead =<< Xmlbf.pAttr "d"-    when (d == 0) (fail "denominator is zero")-    maybe empty pure (mkSomeExchangeRate src dst (n % d))-#endif------------------------------------------------------------------------------------- Extra instances: store-#ifdef HAS_store--- | Compatible with 'SomeDense'.-instance (KnownSymbol currency) => Store.Store (Dense currency) where-  size = storeContramapSize toSomeDense Store.size-  poke = Store.poke . toSomeDense-  peek = maybe (fail "peek") pure =<< fmap fromSomeDense Store.peek--- | Compatible with 'Dense'.-instance Store.Store SomeDense where-  poke = \(SomeDense c r) -> do-    Store.poke c-    Store.poke (numerator r)-    Store.poke (denominator r)-  peek = maybe (fail "peek") pure =<< do-    c :: String <- Store.peek-    n :: Integer <- Store.peek-    d :: Integer <- Store.peek-    when (d == 0) (fail "denominator is zero")-    pure (mkSomeDense c (n % d))---- | Compatible with 'SomeDiscrete'.-instance-  ( KnownSymbol currency, GoodScale scale-  ) => Store.Store (Discrete' currency scale) where-  size = storeContramapSize toSomeDiscrete Store.size-  poke = Store.poke . toSomeDiscrete-  peek = maybe (fail "peek") pure =<< fmap fromSomeDiscrete Store.peek--- | Compatible with 'Discrete''.-instance Store.Store SomeDiscrete where-  poke = \(SomeDiscrete c r a) ->do-    Store.poke c-    Store.poke (numerator r)-    Store.poke (denominator r)-    Store.poke a-  peek = maybe (fail "peek") pure =<< do-    c :: String <- Store.peek-    n :: Integer <- Store.peek-    d :: Integer <- Store.peek-    when (d == 0) (fail "denominator is zero")-    a :: Integer <- Store.peek-    pure (mkSomeDiscrete c (n % d) a)--- | Compatible with 'SomeExchangeRate'.-instance-  ( KnownSymbol src, KnownSymbol dst-  ) => Store.Store (ExchangeRate src dst) where-  size = storeContramapSize toSomeExchangeRate Store.size-  poke = Store.poke . toSomeExchangeRate-  peek = maybe (fail "peek") pure =<< fmap fromSomeExchangeRate Store.peek--- | Compatible with 'ExchangeRate'.-instance Store.Store SomeExchangeRate where-  poke = \(SomeExchangeRate src dst r) -> do-    Store.poke src-    Store.poke dst-    Store.poke (numerator r)-    Store.poke (denominator r)-  peek = maybe (fail "peek") pure =<< do-    src <- Store.peek-    dst <- Store.peek-    n <- Store.peek-    d <- Store.peek-    when (d == 0) (fail "denominator is zero")-    pure (mkSomeExchangeRate src dst (n % d))--storeContramapSize :: (a -> b) -> Store.Size b -> Store.Size a-storeContramapSize f = \case-  Store.VarSize g -> Store.VarSize (g . f)-  Store.ConstSize x -> Store.ConstSize x-{-# INLINABLE storeContramapSize #-}-#endif+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_HADDOCK hide #-}+{-# OPTIONS_GHC -Wno-redundant-constraints #-}++-- | This is an internal module. You may use stuff exported from here, but we+-- can't garantee their stability.+module Money.Internal+ ( -- * Dense monetary values+   Dense+ , denseCurrency+ , dense+ , dense'+ , denseFromDiscrete+ , denseFromDecimal+ , denseToDecimal+   -- * Discrete monetary values+ , Discrete+ , Discrete'+ , discrete+ , discreteCurrency+ , discreteFromDense+ , discreteFromDecimal+   -- * Currency scales+ , Scale+ , GoodScale+ , ErrScaleNonCanonical+ , scale+   -- * Currency exchange+ , ExchangeRate+ , exchangeRate+ , exchange+ , exchangeRateFromDecimal+ , exchangeRateToDecimal+ , exchangeRateToRational+ , exchangeRateRecip+   -- * Serializable representations+ , SomeDense+ , toSomeDense+ , mkSomeDense+ , fromSomeDense+ , withSomeDense+ , someDenseCurrency+ , someDenseAmount+ , SomeDiscrete+ , toSomeDiscrete+ , mkSomeDiscrete+ , fromSomeDiscrete+ , withSomeDiscrete+ , someDiscreteCurrency+ , someDiscreteScale+ , someDiscreteAmount+ , SomeExchangeRate+ , toSomeExchangeRate+ , mkSomeExchangeRate+ , fromSomeExchangeRate+ , withSomeExchangeRate+ , someExchangeRateSrcCurrency+ , someExchangeRateDstCurrency+ , someExchangeRateRate+ -- * Misc+ , Approximation(Round, Floor, Ceiling, Truncate)+ , rationalToDecimal+ , rationalFromDecimal+ ) where++import Control.Applicative ((<|>), empty)+import Control.Category (Category((.), id))+import Control.Monad ((<=<), guard, when)+import qualified Data.Char as Char+import Data.Constraint (Dict(Dict))+import Data.Functor (($>))+import Data.Foldable (for_)+import qualified Data.List as List+import Data.Monoid ((<>))+import Data.Proxy (Proxy(..))+import Data.Ratio ((%), numerator, denominator)+import Data.Word (Word8)+import GHC.Exts (Constraint, fromList)+import qualified GHC.Generics as GHC+import GHC.TypeLits+  (Symbol, SomeSymbol(..), Nat, SomeNat(..), CmpNat, KnownSymbol, KnownNat,+   natVal, someNatVal, symbolVal, someSymbolVal)+import qualified GHC.TypeLits as GHC+import Numeric.Natural (Natural)+import Prelude hiding ((.), id)+import qualified Text.ParserCombinators.ReadPrec as ReadPrec+import qualified Text.ParserCombinators.ReadP as ReadP+import qualified Text.Read as Read+import Unsafe.Coerce (unsafeCoerce)++#ifdef HAS_aeson+import qualified Data.Aeson as Ae+import qualified Data.Text as T+#endif++#ifdef HAS_binary+import qualified Data.Binary as Binary+#endif++#ifdef HAS_cereal+import qualified Data.Serialize as Cereal+#endif++#ifdef HAS_deepseq+import Control.DeepSeq (NFData)+#endif++#ifdef HAS_hashable+import Data.Hashable (Hashable)+#endif++#ifdef HAS_serialise+import qualified Codec.Serialise as Ser+#endif++#ifdef HAS_store+import qualified Data.Store as Store+#endif++#ifdef HAS_vector_space+import qualified Data.AdditiveGroup as AG+import qualified Data.VectorSpace as VS+#endif++#ifdef HAS_xmlbf+import qualified Xmlbf+#endif++--------------------------------------------------------------------------------+-- | 'Dense' represents a dense monetary value for @currency@ (usually a+-- ISO-4217 currency code, but not necessarily) as a rational number.+--+-- While monetary values associated with a particular currency are+-- discrete (e.g., an exact number of coins and bills), you can still treat+-- monetary values as dense while operating on them. For example, the half+-- of @USD 3.41@ is @USD 1.705@, which is not an amount that can't be+-- represented as a number of USD cents (the smallest unit that can+-- represent USD amounts). Nevertheless, if you do manage to represent @USD+-- 1.709@ somehow, and you eventually multiply @USD 1.705@ by @4@ for+-- example, then you end up with @USD 6.82@, which is again a value+-- representable as USD cents. In other words, 'Dense' monetary values+-- allow us to perform precise calculations deferring the conversion to a+-- 'Discrete' monetary values as much as posible. Once you are ready to+-- approximate a 'Dense' value to a 'Discrete' value you can use one+-- 'discreteFromDense'. Otherwise, using 'toRational' you can obtain a+-- precise 'Rational' representation.++-- Construct 'Dense' monetary values using 'dense', 'dense'',+-- 'denseFromDiscrete', 'denseFromDecimal'.+--+-- /WARNING/ if you want to treat a dense monetary value as a /Real/ number+-- like 'Float' or 'Double', then you are on your own. We can only+-- guarantee lossless manipulation of rational values, so you will need to+-- convert back and forth betwen the 'Rational' representation for 'Dense'+-- and your (likely lossy) representation for /Real/ numbers.+newtype Dense (currency :: Symbol) = Dense Rational+  deriving (Eq, Ord, Real, GHC.Generic)++-- | Notice that multiplication of 'Dense' values doesn't make sense:+--+-- @+-- ('*') :: 'Dense' currency -> 'Dense' currency -> 'Dense' currency+-- @+--+-- How is '*' implemented, then? It behaves as the /scalar multiplication/ of a+-- 'Dense' amount by a 'Rational' scalar. That is, you can think of '*' as+-- having one of the the following types:+--+-- @+-- ('*') :: 'Rational' -> 'Dense' currency -> 'Dense' currency+-- @+--+-- @+-- ('*') :: 'Dense' currency -> 'Rational' -> 'Dense' currency@+-- @+--+-- That is:+--+-- @+-- 'dense'' (1 '%' 4) '*' 'dense'' (1 '%' 2)  ==  'dense'' (1 '%' 8)+-- @+--+-- In fact, if you compiled this library with support for+-- 'Data.VectorSpace.VectorSpace', then '*' functions exactly as+-- 'Data.VectorSpace.*^'.+--+-- @+-- ('*')  ==  ('Data.VectorSpace.*^')+-- @+--+-- @+-- ('*')  ==  'flip' ('Data.VectorSpace.*^')+-- @+deriving instance Num (Dense currency)++type family ErrFractionalDense :: Constraint where+  ErrFractionalDense+    = GHC.TypeError+      (('GHC.Text "The ") 'GHC.:<>:+       ('GHC.ShowType Dense) 'GHC.:<>:+       ('GHC.Text " type is deliberately not an instance of ") 'GHC.:<>:+       ('GHC.ShowType Fractional) 'GHC.:$$:+       ('GHC.Text "because functions like 'recip' and '/' can diverge.") 'GHC.:$$:+       ('GHC.Text "Temporarily convert the ") 'GHC.:<>:+       ('GHC.ShowType Dense) 'GHC.:<>:+       ('GHC.Text " value to a ") 'GHC.:<>:+       ('GHC.ShowType Rational) 'GHC.:$$:+       ('GHC.Text " if you know what you are doing."))++instance ErrFractionalDense => Fractional (Dense currency) where+  fromRational = undefined+  recip = undefined++-- |+-- @+-- > 'show' ('dense'' (1 '%' 3) :: 'Dense' \"USD\")+-- \"Dense \\\"USD\\\" 1%3\"+-- @+instance forall currency. KnownSymbol currency => Show (Dense currency) where+  showsPrec n = \(Dense r0) ->+    let c = symbolVal (Proxy :: Proxy currency)+    in showParen (n > 10) $+         showString "Dense " . showsPrec 0 c . showChar ' ' .+         showsPrec 0 (numerator r0) . showChar '%' .+         showsPrec 0 (denominator r0)++instance forall currency. KnownSymbol currency => Read (Dense currency) where+  readPrec = Read.parens $ do+    let c = symbolVal (Proxy :: Proxy currency)+    _ <- ReadPrec.lift (ReadP.string ("Dense " ++ show c ++ " "))+    maybe empty pure =<< fmap dense Read.readPrec++-- | Build a 'Dense' monetary value from a 'Rational' value.+--+-- For example, if you want to represent @USD 12.52316@, then you can use:+--+-- @+-- 'dense' (125316 '%' 10000)+-- @+--+-- Notice that 'dense' returns 'Nothing' in case the given 'Rational''s+-- denominator is zero, which although unlikely, it is possible if the+-- 'Rational' was unsafely constructed. When dealing with hardcoded or trusted+-- 'Rational' values, you can use 'dense'' instead of 'dense' which unsafely+-- constructs a 'Dense'.+dense :: Rational -> Maybe (Dense currency)+dense = \r ->+  if denominator r /= 0+  then Just (Dense r)+  else Nothing+{-# INLINE dense #-}++-- | Unsafely build a 'Dense' monetary value from a 'Rational' value. Contrary+-- to 'dense', this function *crashes* if the given 'Rational' has zero as a+-- denominator, which is something very unlikely to happen unless the given+-- 'Rational' was itself unsafely constructed. Other than that, 'dense' and+-- 'dense'' behave the same.+--+-- Prefer to use 'dense' when dealing with 'Rational' inputs from untrusted+-- sources.+--+-- @+-- 'denominator' x /= 0+--   ⇒ 'dense' x == 'Just' ('dense'' x)+-- @+--+-- @+-- 'denominator' x == 0+--   ⇒ 'undefined' == 'dense'' x+-- @+dense' :: Rational -> Dense currency+dense' = \r ->+  if denominator r /= 0+  then Dense r+  else error "dense': malformed Rational given (denominator is zero)."+{-# INLINABLE dense' #-}++-- | 'Dense' currency identifier.+--+-- @+-- > 'denseCurrency' ('dense'' 4 :: 'Dense' \"USD\")+-- \"USD\"+-- @+denseCurrency :: KnownSymbol currency => Dense currency -> String+denseCurrency = symbolVal+{-# INLINE denseCurrency #-}++-- | 'Discrete' represents a discrete monetary value for a @currency@ expresed+-- as an integer amount of a particular @unit@. For example, with @currency ~+-- \"USD\"@ and @unit ~ \"cent\"@ you can represent United States Dollars to+-- their full extent.+--+-- @currency@ is usually a ISO-4217 currency code, but not necessarily.+--+-- Construct 'Discrete' values using 'discrete', 'fromIntegral', 'fromInteger',+-- 'discreteFromDense', 'discreteFromDecimal'.+--+-- For example, if you want to represent @GBP 21.05@, where the smallest+-- represetable unit for a GBP (United Kingdom Pound) is the /penny/, and 100+-- /pennies/ equal 1 GBP (i.e., @'Scale' \"GBP\" ~ '(100, 1)@), then you can+-- use:+--+-- @+-- 'discrete' 2105 :: 'Discrete' \"GBP\" \"penny\"+-- @+--+-- Because @2015 / 100 == 20.15@.+type Discrete (currency :: Symbol) (unit :: Symbol)+  = Discrete' currency (Scale currency unit)++-- | 'Discrete'' represents a discrete monetary value for a @currency@ expresed+-- as amount of @scale@, which is a rational number expressed as @(numerator,+-- denominator)@.+--+-- You'll be using 'Discrete' instead of 'Discrete'' most of the time, which+-- mentions the unit name (such as /cent/ or /centavo/) instead of explicitely+-- mentioning the unit scale.+newtype Discrete' (currency :: Symbol) (scale :: (Nat, Nat))+  = Discrete Integer++deriving instance GoodScale scale => Eq (Discrete' currency scale)+deriving instance GoodScale scale => Ord (Discrete' currency scale)+deriving instance GoodScale scale => Enum (Discrete' currency scale)+deriving instance GoodScale scale => Real (Discrete' currency scale)+deriving instance GoodScale scale => Integral (Discrete' currency scale)+deriving instance GoodScale scale => GHC.Generic (Discrete' currency scale)++-- | Notice that multiplication of 'Discrete'' values doesn't make sense:+--+-- @+-- ('*') :: 'Discrete'' currency scale -> 'Discrete'' currency scale -> 'Discrete'' currency scale+-- @+--+-- How is '*' implemented, then? It behaves as the /scalar multiplication/ of a+-- 'Discrete'' amount by an 'Integer' scalar. That is, you can think of '*' as+-- having one of the the following types:+--+-- @+-- ('*') :: 'Integer' -> 'Discrete'' currency scale -> 'Discrete'' currency scale+-- @+--+-- @+-- ('*') :: 'Discrete'' currency scale -> 'Integer' -> 'Discrete'' currency scale@+-- @+--+-- That is:+--+-- @+-- 'discrete' 2 '*' 'discrete' 4  ==  'discrete' 8+-- @+--+-- In fact, if you compiled this library with support for+-- 'Data.VectorSpace.VectorSpace', then '*' functions exactly as+-- 'Data.VectorSpace.*^'.+--+-- @+-- ('*')  ==  ('Data.VectorSpace.*^')+-- @+--+-- @+-- ('*')  ==  'flip' ('Data.VectorSpace.*^')+-- @+deriving instance GoodScale scale => Num (Discrete' currency scale)++-- |+-- @+-- > 'show' ('discrete' 123 :: 'Discrete' \"USD\" \"cent\")+-- \"Discrete \\\"USD\\\" 100%1 123\"+-- @+instance forall currency scale.+  ( KnownSymbol currency, GoodScale scale+  ) => Show (Discrete' currency scale) where+  showsPrec n = \d0@(Discrete i0) ->+    let c = symbolVal (Proxy :: Proxy currency)+        s = scale d0+    in showParen (n > 10) $+         showString "Discrete " .  showsPrec 0 c . showChar ' ' .+         showsPrec 0 (numerator s) . showChar '%' .+         showsPrec 0 (denominator s) . showChar ' ' .+         showsPrec 0 i0++instance forall currency scale.+  ( KnownSymbol currency, GoodScale scale+  ) => Read (Discrete' currency scale) where+  readPrec = Read.parens $ do+    let c = symbolVal (Proxy :: Proxy currency)+        s = scale (Proxy :: Proxy scale)+    _ <- ReadPrec.lift (ReadP.string (concat+           [ "Discrete ", show c, " "+           , show (numerator s), "%"+           , show (denominator s), " "+           ]))+    fmap Discrete Read.readPrec++type family ErrFractionalDiscrete :: Constraint where+  ErrFractionalDiscrete+    = GHC.TypeError+        (('GHC.Text "The ") 'GHC.:<>:+         ('GHC.ShowType Discrete') 'GHC.:<>:+         ('GHC.Text " type is deliberately not a ") 'GHC.:<>:+         ('GHC.ShowType Fractional) 'GHC.:$$:+         ('GHC.Text "instance. Convert the ") 'GHC.:<>:+         ('GHC.ShowType Discrete') 'GHC.:<>:+         ('GHC.Text " value to a ") 'GHC.:<>:+         ('GHC.ShowType Dense) 'GHC.:$$:+         ('GHC.Text "value and use the ") 'GHC.:<>:+         ('GHC.ShowType Fractional) 'GHC.:<>:+         ('GHC.Text " features on it instead."))++instance+  ( ErrFractionalDiscrete+  , GoodScale scale+  ) => Fractional (Discrete' currency scale) where+  fromRational = undefined+  recip = undefined++-- | Construct a 'Discrete' value.+discrete :: GoodScale scale => Integer -> Discrete' currency scale+discrete = Discrete+{-# INLINE discrete #-}+++-- | Convert currency 'Discrete' monetary value into a 'Dense' monetary+-- value.+denseFromDiscrete+  :: GoodScale scale+  => Discrete' currency scale+  -> Dense currency -- ^+denseFromDiscrete = \c@(Discrete i) -> Dense (fromInteger i / scale c)+{-# INLINE denseFromDiscrete #-}++-- | 'Discrete' currency identifier.+--+-- @+-- > 'discreteCurrency' ('discrete' 4 :: 'Discrete' \"USD\" \"cent\")+-- \"USD\"+-- @+discreteCurrency+  :: forall currency scale+  .  (KnownSymbol currency, GoodScale scale)+  => Discrete' currency scale+  -> String -- ^+discreteCurrency = \_ -> symbolVal (Proxy :: Proxy currency)+{-# INLINE discreteCurrency #-}++-- | Method for approximating a fractional number to an integer number.+data Approximation+  = Round+  -- ^ Approximate @x@ to the nearest integer, or to the nearest even integer if+  -- @x@ is equidistant between two integers.+  | Floor+  -- ^ Approximate @x@ to the nearest integer less than or equal to @x@.+  | Ceiling+  -- ^ Approximate @x@ to the nearest integer greater than or equal to @x@.+  | Truncate+  -- ^ Approximate @x@ to the nearest integer betwen @0@ and @x@, inclusive.+  deriving (Eq, Ord, Show, Read, GHC.Generic)+++approximate :: Approximation -> Rational -> Integer+{-# INLINE approximate #-}+approximate = \case+  Round -> round+  Floor -> floor+  Ceiling -> ceiling+  Truncate -> truncate++-- | Approximate a 'Dense' value @x@ to the nearest value fully representable a+-- given @scale@.+--+-- If the given 'Dense' doesn't fit entirely in the @scale@, then a non-zero+-- 'Dense' reminder is returned alongside the 'Discrete' approximation.+--+-- Proof that 'discreteFromDense' doesn't lose money:+--+-- @+-- x == case 'discreteFromDense' a x of+--         (y, z) -> 'denseFromDiscrete' y + z+-- @+discreteFromDense+  :: forall currency scale+  .  GoodScale scale+  => Approximation+  -- ^ Approximation to use if necesary in order to fit the 'Dense' amount in+  -- the requested @scale@.+  -> Dense currency+  -> (Discrete' currency scale, Dense currency)+discreteFromDense a = \c0 ->+  let !r0 = toRational c0 :: Rational+      !r1 = scale (Proxy :: Proxy scale)+      !i2 = approximate a (r0 * r1) :: Integer+      !r2 = fromInteger i2 / r1 :: Rational+      !d2 = Discrete i2+      !rest = Dense (r0 - r2)+  in (d2, rest)+{-# INLINABLE discreteFromDense #-}++--------------------------------------------------------------------------------++-- | @'Scale' currency unit@ is an rational number (expressed as @'(numerator,+-- denominator)@) indicating how many pieces of @unit@ fit in @currency@.+--+-- @currency@ is usually a ISO-4217 currency code, but not necessarily.+--+-- The 'Scale' will determine how to convert a 'Dense' value into a+-- 'Discrete' value and vice-versa.+--+-- For example, there are 100 USD cents in 1 USD, so the scale for this+-- relationship is:+--+-- @+-- type instance 'Scale' \"USD\" \"cent\" = '(100, 1)+-- @+--+-- As another example, there is 1 dollar in USD, so the scale for this+-- relationship is:+--+-- @+-- type instance 'Scale' \"USD\" \"dollar\" = '(1, 1)+-- @+--+-- When using 'Discrete' values to represent money, it will be impossible to+-- represent an amount of @currency@ smaller than @unit@. So, if you decide to+-- use @Scale \"USD\" \"dollar\"@ as your scale, you will not be able to+-- represent values such as USD 3.50 or USD 21.87 becacuse they are not exact+-- multiples of a dollar.+--+-- If there exists a canonical smallest @unit@ that can fully represent the+-- currency in all its denominations, then an instance @'Scale' currency+-- currency@ exists.+--+-- @+-- type instance 'Scale' \"USD\" \"USD\" = 'Scale' \"USD\" \"cent\"+-- @+--+-- For some monetary values, such as precious metals, there is no smallest+-- representable unit, since you can repeatedly split the precious metal many+-- times before it stops being a precious metal. Nevertheless, for practical+-- purposes we can make a sane arbitrary choice of smallest unit. For example,+-- the base unit for XAU (Gold) is the /troy ounce/, which is too big to be+-- considered the smallest unit, but we can arbitrarily choose the /milligrain/+-- as our smallest unit, which is about as heavy as a single grain of table salt+-- and should be sufficiently precise for all monetary practical purposes. A+-- /troy ounce/ equals 480000 /milligrains/.+--+-- @+-- type instance 'Scale' \"XAU\" \"milligrain\" = '(480000, 1)+-- @+--+-- You can use other units such as /milligrams/ for measuring XAU, for example.+-- However, since the amount of /milligrams/ in a /troy ounce/ (31103.477) is+-- not integral, we need to use rational with a denominator different than 1 to+-- express it.+--+-- @+-- type instance 'Scale' \"XAU\" \"milligram\" = '(31103477, 1000)+-- @+--+-- If you try to obtain the 'Scale' of a @currency@ without an obvious smallest+-- representable @unit@, like XAU, you will get a compile error.+type family Scale (currency :: Symbol) (unit :: Symbol) :: (Nat, Nat)++-- | A friendly 'GHC.TypeError' to use for a @currency@ that doesn't have a+-- canonical small unit.+type family ErrScaleNonCanonical (currency :: Symbol) :: k where+  ErrScaleNonCanonical c = GHC.TypeError+    ( 'GHC.Text c 'GHC.:<>:+      'GHC.Text " is not a currency with a canonical smallest unit," 'GHC.:$$:+      'GHC.Text "be explicit about the currency unit you want to use." )++-- | Constraints to a scale (like the one returned by @'Scale' currency unit@)+-- expected to always be satisfied. In particular, the scale is always+-- guaranteed to be a positive rational number ('GHC.Real.infinity' and+-- 'GHC.Real.notANumber' are forbidden by 'GoodScale').+type GoodScale (scale :: (Nat, Nat))+   = ( CmpNat 0 (Fst scale) ~ 'LT+     , CmpNat 0 (Snd scale) ~ 'LT+     , KnownNat (Fst scale)+     , KnownNat (Snd scale)+     )++-- | If the specified @num@ and @den@ satisfy the expectations of 'GoodScale' at+-- the type level, then construct a proof for 'GoodScale'.+mkGoodScale+  :: forall num den+  .  (KnownNat num, KnownNat den)+  => Maybe (Dict (GoodScale '(num, den)))+mkGoodScale =+  let n = natVal (Proxy :: Proxy num)+      d = natVal (Proxy :: Proxy den)+  in if (n > 0) && (d > 0)+     then Just (unsafeCoerce (Dict :: Dict ('LT ~ 'LT, 'LT ~ 'LT,+                                            KnownNat num, KnownNat den)))+     else Nothing+{-# INLINABLE mkGoodScale #-}++-- | Term-level representation of a currrency @scale@.+--+-- For example, the 'Scale' for @\"USD\"@ in @\"cent\"@s is @100/1@.+--+-- @+-- > 'scale' ('Proxy' :: 'Proxy' ('Scale' \"USD\" \"cent\"))+-- 100 '%' 1+-- @+--+-- @+-- > 'scale' (x :: 'Discrete' \"USD\" \"cent\")+-- 100 '%' 1+-- @+--+-- The returned 'Rational' is statically guaranteed to be a positive number.+scale :: forall proxy scale. GoodScale scale => proxy scale -> Rational -- ^+scale = \_ -> natVal (Proxy :: Proxy (Fst scale)) %+              natVal (Proxy :: Proxy (Snd scale))+{-# INLINE scale #-}++--------------------------------------------------------------------------------++-- | Exchange rate for converting monetary values of currency @src@ into+-- monetary values of currency @dst@ by multiplying for it.+--+-- For example, if in order to convert USD to GBP we have to multiply by 1.2345,+-- then we can represent this situaion using:+--+-- @+-- 'exchangeRate' (12345 '%' 10000) :: 'Maybe' ('ExchangeRate' \"USD\" \"GBP\")+-- @+newtype ExchangeRate (src :: Symbol) (dst :: Symbol) = ExchangeRate Rational+  deriving (Eq, Ord, GHC.Generic)+++-- | Composition of 'ExchangeRate's multiplies exchange rates together:+--+-- @+-- 'exchangeRateToRational' x * 'exchangeRateToRational' y  ==  'exchangeRateToRational' (x . y)+-- @+--+-- Identity:+--+-- @+-- x  ==  x . id  ==  id . x+-- @+--+-- Associativity:+--+-- @+-- x . y . z  ==  x . (y . z)  ==  (x . y) . z+-- @+--+-- Conmutativity (provided the types allow for composition):+--+-- @+-- x . y  ==  y . x+-- @+--+-- Reciprocal:+--+-- @+-- 1  ==  'exchangeRateToRational' (x . 'exchangeRateRecip' x)+-- @+instance Category ExchangeRate where+  id = ExchangeRate 1+  {-# INLINE id #-}+  ExchangeRate a . ExchangeRate b = ExchangeRate (a * b)+  {-# INLINE (.) #-}++-- |+-- @+-- > 'show' ('exchangeRate' (5 '%' 7) :: 'Maybe' ('ExchangeRate' \"USD\" \"JPY\"))@+-- Just \"ExchangeRate \\\"USD\\\" \\\"JPY\\\" 5%7\"+-- @+instance forall src dst.+  ( KnownSymbol src, KnownSymbol dst+  ) => Show (ExchangeRate src dst) where+  showsPrec n = \(ExchangeRate r0) ->+    let s = symbolVal (Proxy :: Proxy src)+        d = symbolVal (Proxy :: Proxy dst)+    in showParen (n > 10) $+         showString "ExchangeRate " . showsPrec 0 s . showChar ' ' .+         showsPrec 0 d . showChar ' ' .+         showsPrec 0 (numerator r0) . showChar '%' .+         showsPrec 0 (denominator r0)++instance forall src dst.+  ( KnownSymbol src, KnownSymbol dst+  ) => Read (ExchangeRate (src :: Symbol) (dst :: Symbol)) where+  readPrec = Read.parens $ do+    let s = symbolVal (Proxy :: Proxy src)+        d = symbolVal (Proxy :: Proxy dst)+    _ <- ReadPrec.lift (ReadP.string+            ("ExchangeRate " ++ show s ++ " " ++ show d ++ " "))+    maybe empty pure =<< fmap exchangeRate Read.readPrec+++-- | Obtain a 'Rational' representation of the 'ExchangeRate'.+--+-- This 'Rational' is guaranteed to be a positive number.+exchangeRateToRational :: ExchangeRate src dst -> Rational+exchangeRateToRational = \(ExchangeRate r0) -> r0+{-# INLINE exchangeRateToRational #-}++-- | Safely construct an 'ExchangeRate' from a *positive* 'Rational' number.+exchangeRate :: Rational -> Maybe (ExchangeRate src dst)+exchangeRate = \r ->+  if denominator r /= 0 && r > 0+  then Just (ExchangeRate r)+  else Nothing+{-# INLINE exchangeRate #-}++-- | Reciprocal 'ExchangeRate'.+--+-- This function retuns the reciprocal or multiplicative inverse of the given+-- 'ExchangeRate', leading to the following identity law:+--+-- @+-- 'exchangeRateRecip' . 'exchangeRateRecip'   ==  'id'+-- @+--+-- Note: If 'ExchangeRate' had a 'Fractional' instance, then 'exchangeRateRecip'+-- would be the implementation of 'recip'.+exchangeRateRecip :: ExchangeRate a b -> ExchangeRate b a+exchangeRateRecip = \(ExchangeRate x) ->+   ExchangeRate (1 / x)   -- 'exchangeRate' guarantees that @x@ isn't zero.+{-# INLINE exchangeRateRecip #-}++-- | Apply the 'ExchangeRate' to the given @'Dense' src@ monetary value.+--+-- Identity law:+--+-- @+-- 'exchange' ('exchangeRateRecip' x) . 'exchange' x  ==  'id'+-- @+--+-- Use the /Identity law/ for reasoning about going back and forth between @src@+-- and @dst@ in order to manage any leftovers that might not be representable as+-- a 'Discrete' monetary value of @src@.+exchange :: ExchangeRate src dst -> Dense src -> Dense dst+exchange (ExchangeRate r) = \(Dense s) -> Dense (r * s)+{-# INLINE exchange #-}++--------------------------------------------------------------------------------+-- SomeDense++-- | A monomorphic representation of 'Dense' that is easier to serialize and+-- deserialize than 'Dense' in case you don't know the type indexes involved.+--+-- If you are trying to construct a value of this type from some raw input, then+-- you will need to use the 'mkSomeDense' function.+--+-- In order to be able to effectively serialize a 'SomeDense' value, you+-- need to serialize the following three values (which are the eventual+-- arguments to 'mkSomeDense'):+--+-- * 'someDenseCurrency'+-- * 'someDenseAmount'+data SomeDense = SomeDense+  { _someDenseCurrency          :: !String+  , _someDenseAmount            :: !Rational+  } deriving (Eq, Show, GHC.Generic)++-- | __WARNING__ This instance does not compare monetary amounts, it just helps+-- you sort 'SomeDense' values in case you need to put them in a 'Data.Set.Set'+-- or similar.+deriving instance Ord SomeDense++-- | Currency name.+someDenseCurrency :: SomeDense -> String+someDenseCurrency = _someDenseCurrency+{-# INLINE someDenseCurrency #-}++-- | Currency unit amount.+someDenseAmount :: SomeDense -> Rational+someDenseAmount = _someDenseAmount+{-# INLINE someDenseAmount #-}++-- | Build a 'SomeDense' from raw values.+--+-- This function is intended for deserialization purposes. You need to convert+-- this 'SomeDense' value to a 'Dense' value in order to do any arithmetic+-- operation on the monetary value.+mkSomeDense+  :: String   -- ^ Currency. ('someDenseCurrency')+  -> Rational -- ^ Scale. ('someDenseAmount')+  -> Maybe SomeDense+mkSomeDense = \c r ->+  if (denominator r /= 0)+  then Just (SomeDense c r)+  else Nothing+{-# INLINABLE mkSomeDense #-}++-- | Convert a 'Dense' to a 'SomeDense' for ease of serialization.+toSomeDense :: KnownSymbol currency => Dense currency -> SomeDense+toSomeDense = \(Dense r0 :: Dense currency) ->+  let c = symbolVal (Proxy :: Proxy currency)+  in SomeDense c r0+{-# INLINE toSomeDense #-}++-- | Attempt to convert a 'SomeDense' to a 'Dense', provided you know the target+-- @currency@.+fromSomeDense+  :: forall currency+  .  KnownSymbol currency+  => SomeDense+  -> Maybe (Dense currency)  -- ^+fromSomeDense = \dr ->+  if (someDenseCurrency dr == symbolVal (Proxy :: Proxy currency))+  then Just (Dense (someDenseAmount dr))+  else Nothing+{-# INLINABLE fromSomeDense #-}++-- | Convert a 'SomeDense' to a 'Dense' without knowing the target @currency@.+--+-- Notice that @currency@ here can't leave its intended scope unless you can+-- prove equality with some other type at the outer scope, but in that case you+-- would be better off using 'fromSomeDense' directly.+withSomeDense+  :: SomeDense+  -> (forall currency. KnownSymbol currency => Dense currency -> r)+  -> r  -- ^+withSomeDense dr = \f ->+   case someSymbolVal (someDenseCurrency dr) of+      SomeSymbol (Proxy :: Proxy currency) ->+         f (Dense (someDenseAmount dr) :: Dense currency)+{-# INLINABLE withSomeDense #-}++--------------------------------------------------------------------------------+-- SomeDiscrete++-- | A monomorphic representation of 'Discrete' that is easier to serialize and+-- deserialize than 'Discrete' in case you don't know the type indexes involved.+--+-- If you are trying to construct a value of this type from some raw input, then+-- you will need to use the 'mkSomeDiscrete' function.+--+-- In order to be able to effectively serialize a 'SomeDiscrete' value, you need+-- to serialize the following four values (which are the eventual arguments to+-- 'mkSomeDiscrete'):+--+-- * 'someDiscreteCurrency'+-- * 'someDiscreteScale'+-- * 'someDiscreteAmount'+data SomeDiscrete = SomeDiscrete+  { _someDiscreteCurrency :: !String   -- ^ Currency name.+  , _someDiscreteScale    :: !Rational -- ^ Positive, non-zero.+  , _someDiscreteAmount   :: !Integer  -- ^ Amount of unit.+  } deriving (Eq, Show, GHC.Generic)++-- | __WARNING__ This instance does not compare monetary amounts, it just helps+-- you sort 'SomeDiscrete' values in case you need to put them in a+-- 'Data.Set.Set' or similar.+deriving instance Ord SomeDiscrete++-- | Currency name.+someDiscreteCurrency :: SomeDiscrete -> String+someDiscreteCurrency = _someDiscreteCurrency+{-# INLINE someDiscreteCurrency #-}++-- | Positive, non-zero.+someDiscreteScale :: SomeDiscrete -> Rational+someDiscreteScale = _someDiscreteScale+{-# INLINE someDiscreteScale #-}++-- | Amount of currency unit.+someDiscreteAmount :: SomeDiscrete -> Integer+someDiscreteAmount = _someDiscreteAmount+{-# INLINE someDiscreteAmount #-}++-- | Internal. Build a 'SomeDiscrete' from raw values.+--+-- This function is intended for deserialization purposes. You need to convert+-- this 'SomeDiscrete' value to a 'Discrete' vallue in order to do any arithmetic+-- operation on the monetary value.+mkSomeDiscrete+  :: String   -- ^ Currency name. ('someDiscreteCurrency')+  -> Rational -- ^ Scale. Positive, non-zero. ('someDiscreteScale')+  -> Integer  -- ^ Amount of unit. ('someDiscreteAmount')+  -> Maybe SomeDiscrete+mkSomeDiscrete = \c r a ->+  if (denominator r /= 0) && (r > 0)+  then Just (SomeDiscrete c r a)+  else Nothing+{-# INLINABLE mkSomeDiscrete #-}++-- | Convert a 'Discrete' to a 'SomeDiscrete' for ease of serialization.+toSomeDiscrete+  :: (KnownSymbol currency, GoodScale scale)+  => Discrete' currency scale+  -> SomeDiscrete -- ^+toSomeDiscrete = \(Discrete i0 :: Discrete' currency scale) ->+  let c = symbolVal (Proxy :: Proxy currency)+      n = natVal (Proxy :: Proxy (Fst scale))+      d = natVal (Proxy :: Proxy (Snd scale))+  in SomeDiscrete c (n % d) i0+{-# INLINABLE toSomeDiscrete #-}++-- | Attempt to convert a 'SomeDiscrete' to a 'Discrete', provided you know the+-- target @currency@ and @unit@.+fromSomeDiscrete+  :: forall currency scale+  .  (KnownSymbol currency, GoodScale scale)+  => SomeDiscrete+  -> Maybe (Discrete' currency scale)  -- ^+fromSomeDiscrete = \dr ->+   if (someDiscreteCurrency dr == symbolVal (Proxy :: Proxy currency)) &&+      (someDiscreteScale dr == scale (Proxy :: Proxy scale))+   then Just (Discrete (someDiscreteAmount dr))+   else Nothing+{-# INLINABLE fromSomeDiscrete #-}++-- | Convert a 'SomeDiscrete' to a 'Discrete' without knowing the target+-- @currency@ and @unit@.+--+-- Notice that @currency@ and @unit@ here can't leave its intended scope unless+-- you can prove equality with some other type at the outer scope, but in that+-- case you would be better off using 'fromSomeDiscrete' directly.+--+-- Notice that you may need to add an explicit type to the result of this+-- function in order to keep the compiler happy.+withSomeDiscrete+  :: forall r+  .  SomeDiscrete+  -> ( forall currency scale.+         ( KnownSymbol currency+         , GoodScale scale+         ) => Discrete' currency scale+           -> r )+  -> r  -- ^+withSomeDiscrete dr = \f ->+  case someSymbolVal (someDiscreteCurrency dr) of+    SomeSymbol (Proxy :: Proxy currency) ->+      case someNatVal (numerator (someDiscreteScale dr)) of+        Nothing -> error "withSomeDiscrete: impossible: numerator < 0"+        Just (SomeNat (Proxy :: Proxy num)) ->+          case someNatVal (denominator (someDiscreteScale dr)) of+            Nothing -> error "withSomeDiscrete: impossible: denominator < 0"+            Just (SomeNat (Proxy :: Proxy den)) ->+              case mkGoodScale of+                Nothing -> error "withSomeDiscrete: impossible: mkGoodScale"+                Just (Dict :: Dict (GoodScale '(num, den))) ->+                  f (Discrete (someDiscreteAmount dr)+                       :: Discrete' currency '(num, den))+{-# INLINABLE withSomeDiscrete #-}++--------------------------------------------------------------------------------+-- SomeExchangeRate++-- | A monomorphic representation of 'ExchangeRate' that is easier to serialize+-- and deserialize than 'ExchangeRate' in case you don't know the type indexes+-- involved.+--+-- If you are trying to construct a value of this type from some raw input, then+-- you will need to use the 'mkSomeExchangeRate' function.+--+-- In order to be able to effectively serialize an 'SomeExchangeRate' value, you+-- need to serialize the following four values (which are the eventual arguments+-- to 'mkSomeExchangeRate'):+--+-- * 'someExchangeRateSrcCurrency'+-- * 'someExchangeRateDstCurrency'+-- * 'someExchangeRateRate'+data SomeExchangeRate = SomeExchangeRate+  { _someExchangeRateSrcCurrency     :: !String+  , _someExchangeRateDstCurrency     :: !String+  , _someExchangeRateRate            :: !Rational -- ^ Positive, non-zero.+  } deriving (Eq, Show, GHC.Generic)++-- | __WARNING__ This instance does not compare monetary amounts, it just helps+-- you sort 'SomeExchangeRate' values in case you need to put them in a+-- 'Data.Set.Set' or similar.+deriving instance Ord SomeExchangeRate++-- | Source currency name.+someExchangeRateSrcCurrency :: SomeExchangeRate -> String+someExchangeRateSrcCurrency = _someExchangeRateSrcCurrency+{-# INLINE someExchangeRateSrcCurrency #-}++-- | Destination currency name.+someExchangeRateDstCurrency :: SomeExchangeRate -> String+someExchangeRateDstCurrency = _someExchangeRateDstCurrency+{-# INLINE someExchangeRateDstCurrency #-}++-- | Exchange rate. Positive, non-zero.+someExchangeRateRate :: SomeExchangeRate -> Rational+someExchangeRateRate = _someExchangeRateRate+{-# INLINE someExchangeRateRate #-}++-- | Internal. Build a 'SomeExchangeRate' from raw values.+--+-- This function is intended for deserialization purposes. You need to convert+-- this 'SomeExchangeRate' value to a 'ExchangeRate' value in order to do any+-- arithmetic operation with the exchange rate.+mkSomeExchangeRate+  :: String   -- ^ Source currency name. ('someExchangeRateSrcCurrency')+  -> String   -- ^ Destination currency name. ('someExchangeRateDstCurrency')+  -> Rational -- ^ Exchange rate . Positive, non-zero. ('someExchangeRateRate')+  -> Maybe SomeExchangeRate+mkSomeExchangeRate = \src dst r ->+  if (denominator r /= 0) && (r > 0)+  then Just (SomeExchangeRate src dst r)+  else Nothing+{-# INLINABLE mkSomeExchangeRate #-}++-- | Convert a 'ExchangeRate' to a 'SomeDiscrete' for ease of serialization.+toSomeExchangeRate+  :: (KnownSymbol src, KnownSymbol dst)+  => ExchangeRate src dst+  -> SomeExchangeRate -- ^+toSomeExchangeRate = \(ExchangeRate r0 :: ExchangeRate src dst) ->+  let src = symbolVal (Proxy :: Proxy src)+      dst = symbolVal (Proxy :: Proxy dst)+  in SomeExchangeRate src dst r0+{-# INLINABLE toSomeExchangeRate #-}++-- | Attempt to convert a 'SomeExchangeRate' to a 'ExchangeRate', provided you+-- know the target @src@ and @dst@ types.+fromSomeExchangeRate+  :: forall src dst+  .  (KnownSymbol src, KnownSymbol dst)+  => SomeExchangeRate+  -> Maybe (ExchangeRate src dst)  -- ^+fromSomeExchangeRate = \x ->+   if (someExchangeRateSrcCurrency x == symbolVal (Proxy :: Proxy src)) &&+      (someExchangeRateDstCurrency x == symbolVal (Proxy :: Proxy dst))+   then Just (ExchangeRate (someExchangeRateRate x))+   else Nothing+{-# INLINABLE fromSomeExchangeRate #-}++-- | Convert a 'SomeExchangeRate' to a 'ExchangeRate' without knowing the target+-- @currency@ and @unit@.+--+-- Notice that @src@ and @dst@ here can't leave its intended scope unless+-- you can prove equality with some other type at the outer scope, but in that+-- case you would be better off using 'fromSomeExchangeRate' directly.+withSomeExchangeRate+  :: SomeExchangeRate+  -> ( forall src dst.+         ( KnownSymbol src+         , KnownSymbol dst+         ) => ExchangeRate src dst+           -> r )+  -> r  -- ^+withSomeExchangeRate x = \f ->+  case someSymbolVal (someExchangeRateSrcCurrency x) of+    SomeSymbol (Proxy :: Proxy src) ->+      case someSymbolVal (someExchangeRateDstCurrency x) of+        SomeSymbol (Proxy :: Proxy dst) ->+          f (ExchangeRate (someExchangeRateRate x) :: ExchangeRate src dst)+{-# INLINABLE withSomeExchangeRate #-}++--------------------------------------------------------------------------------+-- Miscellaneous++type family Fst (ab :: (ka, kb)) :: ka where Fst '(a,b) = a+type family Snd (ab :: (ka, kb)) :: ka where Snd '(a,b) = b++--------------------------------------------------------------------------------+-- vector-space instances++#ifdef HAS_vector_space+instance AG.AdditiveGroup (Dense currency) where+  zeroV = Dense AG.zeroV+  {-# INLINE zeroV #-}+  Dense a ^+^ Dense b = Dense $! (a AG.^+^ b)+  {-# INLINE (^+^) #-}+  negateV (Dense a) = Dense $! (AG.negateV a)+  {-# INLINE negateV #-}+  Dense a ^-^ Dense b = Dense $! (a AG.^-^ b)+  {-# INLINE (^-^) #-}++-- | __WARNING__ a scalar with a zero denominator will cause 'VS.*^' to crash.+instance VS.VectorSpace (Dense currency) where+  type Scalar (Dense currency) = Rational+  s *^ Dense a =+    if denominator s /= 0+    then Dense $! s VS.*^ a+    else error "(*^)': malformed Rational given (denominator is zero)."+  {-# INLINE (*^) #-}++instance GoodScale scale => AG.AdditiveGroup (Discrete' currency scale) where+  zeroV = Discrete AG.zeroV+  {-# INLINE zeroV #-}+  Discrete a ^+^ Discrete b = Discrete $! (a AG.^+^ b)+  {-# INLINE (^+^) #-}+  negateV (Discrete a) = Discrete $! (AG.negateV a)+  {-# INLINE negateV #-}+  Discrete a ^-^ Discrete b = Discrete $! (a AG.^-^ b)+  {-# INLINE (^-^) #-}++instance GoodScale scale => VS.VectorSpace (Discrete' currency scale) where+  type Scalar (Discrete' currency scale) = Integer+  s *^ Discrete a = Discrete $! (s VS.*^ a)+  {-# INLINE (*^) #-}+#endif++--------------------------------------------------------------------------------+-- Extra instances: hashable+#ifdef HAS_hashable+instance Hashable Approximation+instance Hashable (Dense currency)+instance Hashable SomeDense+instance GoodScale scale => Hashable (Discrete' currency scale)+instance Hashable SomeDiscrete+instance Hashable (ExchangeRate src dst)+instance Hashable SomeExchangeRate+#endif++--------------------------------------------------------------------------------+-- Extra instances: deepseq+#ifdef HAS_deepseq+instance NFData Approximation+instance NFData (Dense currency)+instance NFData SomeDense+instance GoodScale scale => NFData (Discrete' currency scale)+instance NFData SomeDiscrete+instance NFData (ExchangeRate src dst)+instance NFData SomeExchangeRate+#endif++--------------------------------------------------------------------------------+-- Extra instances: cereal+#ifdef HAS_cereal+-- | Compatible with 'SomeDense'.+instance (KnownSymbol currency) => Cereal.Serialize (Dense currency) where+  put = Cereal.put . toSomeDense+  get = maybe empty pure =<< fmap fromSomeDense Cereal.get++-- | Compatible with 'SomeDiscrete'.+instance+  ( KnownSymbol currency, GoodScale scale+  ) => Cereal.Serialize (Discrete' currency scale) where+  put = Cereal.put . toSomeDiscrete+  get = maybe empty pure =<< fmap fromSomeDiscrete Cereal.get++-- | Compatible with 'SomeExchangeRate'.+instance+  ( KnownSymbol src, KnownSymbol dst+  ) => Cereal.Serialize (ExchangeRate src dst) where+  put = Cereal.put . toSomeExchangeRate+  get = maybe empty pure =<< fmap fromSomeExchangeRate Cereal.get++-- | Compatible with 'Dense'.+instance Cereal.Serialize SomeDense where+  put = \(SomeDense c r) -> do+    Cereal.put c+    Cereal.put (numerator r)+    Cereal.put (denominator r)+  get = maybe empty pure =<< do+    c :: String <- Cereal.get+    n :: Integer <- Cereal.get+    d :: Integer <- Cereal.get+    when (d == 0) (fail "denominator is zero")+    pure (mkSomeDense c (n % d))++-- | Compatible with 'Discrete'.+instance Cereal.Serialize SomeDiscrete where+  put = \(SomeDiscrete c r a) -> do+    Cereal.put c+    Cereal.put (numerator r)+    Cereal.put (denominator r)+    Cereal.put a+  get = maybe empty pure =<< do+    c :: String <- Cereal.get+    n :: Integer <- Cereal.get+    d :: Integer <- Cereal.get+    when (d == 0) (fail "denominator is zero")+    a :: Integer <- Cereal.get+    pure (mkSomeDiscrete c (n % d) a)++-- | Compatible with 'ExchangeRate'.+instance Cereal.Serialize SomeExchangeRate where+  put = \(SomeExchangeRate src dst r) -> do+    Cereal.put src+    Cereal.put dst+    Cereal.put (numerator r)+    Cereal.put (denominator r)+  get = maybe empty pure =<< do+    src :: String <- Cereal.get+    dst :: String <- Cereal.get+    n :: Integer <- Cereal.get+    d :: Integer <- Cereal.get+    when (d == 0) (fail "denominator is zero")+    pure (mkSomeExchangeRate src dst (n % d))+#endif++------------------------------------------------------------------------------+-- Extra instances: binary+#ifdef HAS_binary+-- | Compatible with 'SomeDense'.+instance (KnownSymbol currency) => Binary.Binary (Dense currency) where+  put = Binary.put . toSomeDense+  get = maybe empty pure =<< fmap fromSomeDense Binary.get++-- | Compatible with 'SomeDiscrete'.+instance+  ( KnownSymbol currency, GoodScale scale+  ) => Binary.Binary (Discrete' currency scale) where+  put = Binary.put . toSomeDiscrete+  get = maybe empty pure =<< fmap fromSomeDiscrete Binary.get++-- | Compatible with 'SomeExchangeRate'.+instance+  ( KnownSymbol src, KnownSymbol dst+  ) => Binary.Binary (ExchangeRate src dst) where+  put = Binary.put . toSomeExchangeRate+  get = maybe empty pure =<< fmap fromSomeExchangeRate Binary.get++-- | Compatible with 'Dense'.+instance Binary.Binary SomeDense where+  put = \(SomeDense c r) ->+    Binary.put c >> Binary.put (numerator r) >> Binary.put (denominator r)+  get = maybe empty pure =<< do+    c :: String <- Binary.get+    n :: Integer <- Binary.get+    d :: Integer <- Binary.get+    when (d == 0) (fail "denominator is zero")+    pure (mkSomeDense c (n % d))++-- | Compatible with 'Discrete'.+instance Binary.Binary SomeDiscrete where+  put = \(SomeDiscrete c r a) ->+    Binary.put c <>+    Binary.put (numerator r) <>+    Binary.put (denominator r) <>+    Binary.put a+  get = maybe empty pure =<< do+    c :: String <- Binary.get+    n :: Integer <- Binary.get+    d :: Integer <- Binary.get+    when (d == 0) (fail "denominator is zero")+    a :: Integer <- Binary.get+    pure (mkSomeDiscrete c (n % d) a)++-- | Compatible with 'ExchangeRate'.+instance Binary.Binary SomeExchangeRate where+  put = \(SomeExchangeRate src dst r) -> do+    Binary.put src+    Binary.put dst+    Binary.put (numerator r)+    Binary.put (denominator r)+  get = maybe empty pure =<< do+    src :: String <- Binary.get+    dst :: String <- Binary.get+    n :: Integer <- Binary.get+    d :: Integer <- Binary.get+    when (d == 0) (fail "denominator is zero")+    pure (mkSomeExchangeRate src dst (n % d))+#endif++--------------------------------------------------------------------------------+-- Extra instances: serialise+#ifdef HAS_serialise+-- | Compatible with 'SomeDense'.+instance (KnownSymbol currency) => Ser.Serialise (Dense currency) where+  encode = Ser.encode . toSomeDense+  decode = maybe (fail "Dense") pure =<< fmap fromSomeDense Ser.decode++-- | Compatible with 'SomeDiscrete'.+instance+  ( KnownSymbol currency, GoodScale scale+  ) => Ser.Serialise (Discrete' currency scale) where+  encode = Ser.encode . toSomeDiscrete+  decode = maybe (fail "Discrete'") pure =<< fmap fromSomeDiscrete Ser.decode++-- | Compatible with 'SomeExchangeRate'.+instance+  ( KnownSymbol src, KnownSymbol dst+  ) => Ser.Serialise (ExchangeRate src dst) where+  encode = Ser.encode . toSomeExchangeRate+  decode = maybe (fail "ExchangeRate") pure+             =<< fmap fromSomeExchangeRate Ser.decode++-- | Compatible with 'Dense'.+instance Ser.Serialise SomeDense where+  encode = \(SomeDense c r) ->+    Ser.encode c <> Ser.encode (numerator r) <> Ser.encode (denominator r)+  decode = maybe (fail "SomeDense") pure =<< do+    c :: String <- Ser.decode+    n :: Integer <- Ser.decode+    d :: Integer <- Ser.decode+    when (d == 0) (fail "denominator is zero")+    pure (mkSomeDense c (n % d))++-- | Compatible with 'Discrete'.+instance Ser.Serialise SomeDiscrete where+  encode = \(SomeDiscrete c r a) ->+    Ser.encode c <>+    Ser.encode (numerator r) <>+    Ser.encode (denominator r) <>+    Ser.encode a+  decode = maybe (fail "SomeDiscrete") pure =<< do+    c :: String <- Ser.decode+    n :: Integer <- Ser.decode+    d :: Integer <- Ser.decode+    when (d == 0) (fail "denominator is zero")+    a :: Integer <- Ser.decode+    pure (mkSomeDiscrete c (n % d) a)++-- | Compatible with 'ExchangeRate'.+instance Ser.Serialise SomeExchangeRate where+  encode = \(SomeExchangeRate src dst r) ->+    Ser.encode src <>+    Ser.encode dst <>+    Ser.encode (numerator r) <>+    Ser.encode (denominator r)+  decode = maybe (fail "SomeExchangeRate") pure =<< do+    src :: String <- Ser.decode+    dst :: String <- Ser.decode+    n :: Integer <- Ser.decode+    d :: Integer <- Ser.decode+    when (d == 0) (fail "denominator is zero")+    pure (mkSomeExchangeRate src dst (n % d))+#endif++--------------------------------------------------------------------------------+-- Extra instances: aeson+#ifdef HAS_aeson+-- | Compatible with 'SomeDense'+--+-- Example rendering @'dense'' (2 '%' 3) :: 'Dense' \"BTC\"@:+--+-- @+-- [\"BTC\", 2, 3]+-- @+--+-- Note: The JSON serialization changed in version 0.4 (the leading @"Dense"@+-- string was dropped from the rendered 'Ae.Array').+instance KnownSymbol currency => Ae.ToJSON (Dense currency) where+  toJSON = Ae.toJSON . toSomeDense++-- | Compatible with 'SomeDense'+--+-- Note: The JSON serialization changed in version 0.4. However, this instance+-- is still able to cope with the previous format.+instance KnownSymbol currency => Ae.FromJSON (Dense currency) where+  parseJSON = maybe empty pure <=< fmap fromSomeDense . Ae.parseJSON++-- | Compatible with 'Dense'+--+-- Note: The JSON serialization changed in version 0.4 (the leading @"Dense"@+-- string was dropped from the rendered 'Ae.Array').+instance Ae.ToJSON SomeDense where+  toJSON = \(SomeDense c r) ->+    Ae.toJSON (c, numerator r, denominator r)++-- | Compatible with 'Dense'.+--+-- Note: The JSON serialization changed in version 0.4. However, this instance+-- is still able to cope with the previous format.+instance Ae.FromJSON SomeDense where+  parseJSON = \v -> do+    (c, n, d) <- Ae.parseJSON v <|> do+       -- Pre 0.4 format.+       ("Dense" :: String, c, n, d) <- Ae.parseJSON v+       pure (c, n, d)+    when (d == 0) (fail "denominator is zero")+    maybe empty pure (mkSomeDense c (n % d))++-- | Compatible with 'SomeDiscrete'+--+-- Example rendering @'discrete' 43 :: 'Discrete' \"BTC\" \"satoshi\"@:+--+-- @+-- [\"BTC\", 100000000, 1, 43]+-- @+--+-- Note: The JSON serialization changed in version 0.4 (the leading @"Discrete"@+-- string was dropped from the rendered 'Ae.Array').+instance+  ( KnownSymbol currency, GoodScale scale+  ) => Ae.ToJSON (Discrete' currency scale) where+  toJSON = Ae.toJSON . toSomeDiscrete++-- | Compatible with 'SomeDiscrete'+--+-- Note: The JSON serialization changed in version 0.4. However, this instance+-- is still able to cope with the previous format.+instance+  ( KnownSymbol currency, GoodScale scale+  ) => Ae.FromJSON (Discrete' currency scale) where+  parseJSON = maybe empty pure <=< fmap fromSomeDiscrete . Ae.parseJSON++-- | Compatible with 'Discrete''+--+-- Note: The JSON serialization changed in version 0.4 (the leading @"Discrete"@+-- string was dropped from the rendered 'Ae.Array').+instance Ae.ToJSON SomeDiscrete where+  toJSON = \(SomeDiscrete c r a) ->+    Ae.toJSON (c, numerator r, denominator r, a)++-- | Compatible with 'Discrete''+--+-- Note: The JSON serialization changed in version 0.4. However, this instance+-- is still able to cope with the previous format.+instance Ae.FromJSON SomeDiscrete where+  parseJSON = \v -> do+    (c, n, d, a) <- Ae.parseJSON v <|> do+       -- Pre 0.4 format.+       ("Discrete" :: String, c, n, d, a) <- Ae.parseJSON v+       pure (c, n, d, a)+    when (d == 0) (fail "denominator is zero")+    maybe empty pure (mkSomeDiscrete c (n % d) a)++-- | Compatible with 'SomeExchangeRate'+--+-- Example rendering an 'ExchangeRate' constructed with+-- @'exchangeRate' (5 '%' 7) :: 'ExchangeRate' \"USD\" \"JPY\"@+--+-- @+-- [\"USD\", \"JPY\", 5, 7]+-- @+--+-- Note: The JSON serialization changed in version 0.4 (the leading+-- @"ExchangeRate"@ string was dropped from the rendered 'Ae.Array').+instance+  ( KnownSymbol src, KnownSymbol dst+  ) => Ae.ToJSON (ExchangeRate src dst) where+  toJSON = Ae.toJSON . toSomeExchangeRate++-- | Compatible with 'SomeExchangeRate'+--+-- Note: The JSON serialization changed in version 0.4. However, this instance+-- is still able to cope with the previous format.+instance+  ( KnownSymbol src, KnownSymbol dst+  ) => Ae.FromJSON (ExchangeRate src dst) where+  parseJSON = maybe empty pure <=< fmap fromSomeExchangeRate . Ae.parseJSON++-- | Compatible with 'ExchangeRate'+--+-- Note: The JSON serialization changed in version 0.4 (the leading+-- @"ExchangeRate"@ string was dropped from the rendered 'Ae.Array').+instance Ae.ToJSON SomeExchangeRate where+  toJSON = \(SomeExchangeRate src dst r) ->+    Ae.toJSON (src, dst, numerator r, denominator r)++-- | Compatible with 'ExchangeRate'+--+-- Note: The JSON serialization changed in version 0.4. However, this instance+-- is still able to cope with the previous format.+instance Ae.FromJSON SomeExchangeRate where+  parseJSON = \v -> do+    (src, dst, n, d) <- Ae.parseJSON v <|> do+       -- Pre 0.4 format.+       ("ExchangeRate" :: String, src, dst, n, d) <- Ae.parseJSON v+       pure (src, dst, n, d)+    when (d == 0) (fail "denominator is zero")+    maybe empty pure (mkSomeExchangeRate src dst (n % d))+#endif++--------------------------------------------------------------------------------+-- Extra instances: xmlbf+#ifdef HAS_xmlbf++-- | Compatible with 'SomeDense'+--+-- Example rendering @'dense' (2 '%' 3) :: 'Dense' \"BTC\"@:+--+-- @+-- \<money-dense c=\"BTC\" n=\"2\" d=\"3\"/>+-- @+instance KnownSymbol currency => Xmlbf.ToXml (Dense currency) where+  toXml = Xmlbf.toXml . toSomeDense++-- | Compatible with 'SomeDense'+instance KnownSymbol currency => Xmlbf.FromXml (Dense currency) where+  fromXml = maybe empty pure =<< fmap fromSomeDense Xmlbf.fromXml++-- | Compatible with 'Dense'+instance Xmlbf.ToXml SomeDense where+  toXml = \(SomeDense c r) ->+    let as = [ (T.pack "c", T.pack c)+             , (T.pack "n", T.pack (show (numerator r)))+             , (T.pack "d", T.pack (show (denominator r))) ]+        Right e = Xmlbf.element (T.pack "money-dense") (fromList as) []+    in [e]++-- | Compatible with 'Dense'.+instance Xmlbf.FromXml SomeDense where+  fromXml = Xmlbf.pElement (T.pack "money-dense") $ do+    c <- T.unpack <$> Xmlbf.pAttr "c"+    n <- Xmlbf.pRead =<< Xmlbf.pAttr "n"+    d <- Xmlbf.pRead =<< Xmlbf.pAttr "d"+    when (d == 0) (fail "denominator is zero")+    maybe empty pure (mkSomeDense c (n % d))++-- | Compatible with 'SomeDiscrete'+--+-- Example rendering @'discrete' 43 :: 'Discrete' \"BTC\" \"satoshi\"@:+--+-- @+-- \<money-discrete c=\"BTC\" n=\"100000000\" d=\"1\" a=\"43\"/>+-- @+instance+  ( KnownSymbol currency, GoodScale scale+  ) => Xmlbf.ToXml (Discrete' currency scale) where+  toXml = Xmlbf.toXml . toSomeDiscrete++-- | Compatible with 'SomeDiscrete'+instance+  ( KnownSymbol currency, GoodScale scale+  ) => Xmlbf.FromXml (Discrete' currency scale) where+  fromXml = maybe empty pure =<< fmap fromSomeDiscrete Xmlbf.fromXml++-- | Compatible with 'Discrete''+instance Xmlbf.ToXml SomeDiscrete where+  toXml = \(SomeDiscrete c r a) ->+    let as = [ (T.pack "c", T.pack c)+             , (T.pack "n", T.pack (show (numerator r)))+             , (T.pack "d", T.pack (show (denominator r)))+             , (T.pack "a", T.pack (show a)) ]+        Right e = Xmlbf.element (T.pack "money-discrete") (fromList as) []+    in [e]++-- | Compatible with 'Discrete''+instance Xmlbf.FromXml SomeDiscrete where+  fromXml = Xmlbf.pElement (T.pack "money-discrete") $ do+    c <- T.unpack <$> Xmlbf.pAttr "c"+    n <- Xmlbf.pRead =<< Xmlbf.pAttr "n"+    d <- Xmlbf.pRead =<< Xmlbf.pAttr "d"+    when (d == 0) (fail "denominator is zero")+    a <- Xmlbf.pRead =<< Xmlbf.pAttr "a"+    maybe empty pure (mkSomeDiscrete c (n % d) a)++-- | Compatible with 'SomeExchangeRate'+--+-- Example rendering an 'ExchangeRate' constructed with+-- @'exchangeRate' (5 '%' 7) :: 'ExchangeRate' \"USD\" \"JPY\"@+--+-- @+-- \<exchange-rate src=\"USD\" dst=\"JPY\" n=\"5\" d=\"7\"/>+-- @+instance+  ( KnownSymbol src, KnownSymbol dst+  ) => Xmlbf.ToXml (ExchangeRate src dst) where+  toXml = Xmlbf.toXml . toSomeExchangeRate++-- | Compatible with 'SomeExchangeRate'+instance+  ( KnownSymbol src, KnownSymbol dst+  ) => Xmlbf.FromXml (ExchangeRate src dst) where+  fromXml = maybe empty pure =<< fmap fromSomeExchangeRate Xmlbf.fromXml++-- | Compatible with 'ExchangeRate'+instance Xmlbf.ToXml SomeExchangeRate where+  toXml = \(SomeExchangeRate src dst r) ->+    let as = [ (T.pack "src", T.pack src)+             , (T.pack "dst", T.pack dst)+             , (T.pack "n", T.pack (show (numerator r)))+             , (T.pack "d", T.pack (show (denominator r))) ]+        Right e = Xmlbf.element (T.pack "exchange-rate") (fromList as) []+    in [e]++-- | Compatible with 'ExchangeRate'+instance Xmlbf.FromXml SomeExchangeRate where+  fromXml = Xmlbf.pElement (T.pack "exchange-rate") $ do+    src <- T.unpack <$> Xmlbf.pAttr "src"+    dst <- T.unpack <$> Xmlbf.pAttr "dst"+    n <- Xmlbf.pRead =<< Xmlbf.pAttr "n"+    d <- Xmlbf.pRead =<< Xmlbf.pAttr "d"+    when (d == 0) (fail "denominator is zero")+    maybe empty pure (mkSomeExchangeRate src dst (n % d))+#endif++--------------------------------------------------------------------------------+-- Extra instances: store+#ifdef HAS_store+-- | Compatible with 'SomeDense'.+instance (KnownSymbol currency) => Store.Store (Dense currency) where+  size = storeContramapSize toSomeDense Store.size+  poke = Store.poke . toSomeDense+  peek = maybe (fail "peek") pure =<< fmap fromSomeDense Store.peek+-- | Compatible with 'Dense'.+instance Store.Store SomeDense where+  poke = \(SomeDense c r) -> do+    Store.poke c+    Store.poke (numerator r)+    Store.poke (denominator r)+  peek = maybe (fail "peek") pure =<< do+    c :: String <- Store.peek+    n :: Integer <- Store.peek+    d :: Integer <- Store.peek+    when (d == 0) (fail "denominator is zero")+    pure (mkSomeDense c (n % d))++-- | Compatible with 'SomeDiscrete'.+instance+  ( KnownSymbol currency, GoodScale scale+  ) => Store.Store (Discrete' currency scale) where+  size = storeContramapSize toSomeDiscrete Store.size+  poke = Store.poke . toSomeDiscrete+  peek = maybe (fail "peek") pure =<< fmap fromSomeDiscrete Store.peek+-- | Compatible with 'Discrete''.+instance Store.Store SomeDiscrete where+  poke = \(SomeDiscrete c r a) ->do+    Store.poke c+    Store.poke (numerator r)+    Store.poke (denominator r)+    Store.poke a+  peek = maybe (fail "peek") pure =<< do+    c :: String <- Store.peek+    n :: Integer <- Store.peek+    d :: Integer <- Store.peek+    when (d == 0) (fail "denominator is zero")+    a :: Integer <- Store.peek+    pure (mkSomeDiscrete c (n % d) a)+-- | Compatible with 'SomeExchangeRate'.+instance+  ( KnownSymbol src, KnownSymbol dst+  ) => Store.Store (ExchangeRate src dst) where+  size = storeContramapSize toSomeExchangeRate Store.size+  poke = Store.poke . toSomeExchangeRate+  peek = maybe (fail "peek") pure =<< fmap fromSomeExchangeRate Store.peek+-- | Compatible with 'ExchangeRate'.+instance Store.Store SomeExchangeRate where+  poke = \(SomeExchangeRate src dst r) -> do+    Store.poke src+    Store.poke dst+    Store.poke (numerator r)+    Store.poke (denominator r)+  peek = maybe (fail "peek") pure =<< do+    src <- Store.peek+    dst <- Store.peek+    n <- Store.peek+    d <- Store.peek+    when (d == 0) (fail "denominator is zero")+    pure (mkSomeExchangeRate src dst (n % d))++storeContramapSize :: (a -> b) -> Store.Size b -> Store.Size a+storeContramapSize f = \case+  Store.VarSize g -> Store.VarSize (g . f)+  Store.ConstSize x -> Store.ConstSize x+{-# INLINABLE storeContramapSize #-}+#endif++--------------------------------------------------------------------------------+-- Decimal rendering++-- | Render a 'Dense' monetary amount as a decimal number in a potentially lossy+-- manner.+--+-- @+-- > 'denseToDecimal' 'Round' 'True' ('Just' \',\') \'.\' 2+--      ('Proxy' :: 'Proxy' ('Scale' \"USD\" \"dollar\"))+--      ('dense'' (123456 '%' 100) :: 'Dense' \"USD\")+-- Just \"+1,234.56\"+-- @+--+-- @+-- > 'denseToDecimal' 'Round' 'True' ('Just' \',\') \'.\' 2+--      ('Proxy' :: 'Proxy' ('Scale' \"USD\" \"cent\"))+--      ('dense'' (123456 '%' 100) :: 'Dense' \"USD\")+-- Just \"+123,456.00\"+-- @+--+-- This function returns 'Nothing' if it is not possible to reliably render the+-- decimal string due to a bad choice of separators. That is, if the separators+-- are digits or equal among themselves, this function returns 'Nothing'.+denseToDecimal+  :: GoodScale scale+  => Approximation+  -- ^ Approximation to use if necesary in order to fit the 'Dense' amount in+  -- as many decimal numbers as requested.+  -> Bool+  -- ^ Whether to render a leading @\'+\'@ sign in case the amount is positive.+  -> Maybe Char+  -- ^ Thousands separator for the integer part, if any (i.e., the @\',\'@ in+  -- @1,234.56789@).+  -> Char+  -- ^ Decimal separator (i.e., the @\'.\'@ in @1,234.56789@)+  -> Word8+  -- ^ Number of decimal numbers to render, if any.+  -> Proxy scale+  -- ^ Scale used by the integer part of the decimal number. For example, a when+  -- rendering render @'dense'' (123 '%' 100) :: 'Dense' "USD"@ as a decimal+  -- number with three decimal places, a scale of @1@ (i.e. @'Scale' \"USD\"+  -- \"dollar\"@) would render @1@ as the integer part and @230@ as the+  -- fractional part, whereas a scale of @100@ (i.e., @'Scale' \"USD\"+  -- \"cent\"@) would render @123@ as the integer part and @000@ as the+  -- fractional part.+  -> Dense currency+  -- ^ The dense monetary amount to render.+  -> Maybe String+  -- ^ Returns 'Nothing' is the given separators are not acceptable (i.e., they+  -- are digits, or they are equal).+{-# INLINABLE denseToDecimal #-}+denseToDecimal a plus ytsep dsep fdigs0 ps = \(Dense r0) ->+  rationalToDecimal a plus ytsep dsep fdigs0 (scale ps * r0)++-- | Render a 'ExchangeRate' as a decimal number in a potentially lossy manner.+--+-- @+-- > 'exchangeRateToDecimal' 'Round' 'True' ('Just' \',\') \'.\' 2+--       '=<<' ('exchangeRate' (123456 '%' 100) :: 'Maybe' ('ExchangeRate' \"USD\" \"EUR\"))+-- Just \"1,234.56\"+-- @+--+-- This function returns 'Nothing' if it is not possible to reliably render the+-- decimal string due to a bad choice of separators. That is, if the separators+-- are digits or equal among themselves, this function returns 'Nothing'.+exchangeRateToDecimal+  :: Approximation+  -- ^ Approximation to use if necesary in order to fit the 'Dense' amount in+  -- as many decimal numbers as requested.+  -> Maybe Char+  -- ^ Thousands separator for the integer part, if any (i.e., the @\',\'@ in+  -- @1,234.56789@).+  -> Char+  -- ^ Decimal separator (i.e., the @\'.\'@ in @1,234.56789@)+  -> Word8+  -- ^ Number of decimal numbers to render, if any.+  -> ExchangeRate src dst+  -- ^ The 'ExchangeRate' to render.+  -> Maybe String+  -- ^ Returns 'Nothing' is the given separators are not acceptable (i.e., they+  -- are digits, or they are equal).+{-# INLINABLE exchangeRateToDecimal #-}+exchangeRateToDecimal a ytsep dsep fdigs0 = \(ExchangeRate r0) ->+  rationalToDecimal a False ytsep dsep fdigs0 r0++-- | Render a 'Rational' number as a decimal approximation.+--+-- This function returns 'Nothing' if it is not possible to reliably render the+-- decimal string due to a bad choice of separators. That is, if the separators+-- are digits or equal among themselves, this function returns 'Nothing'.+rationalToDecimal+  :: Approximation+  -- ^ Approximation to use if necesary in order to fit the 'Dense' amount in+  -- as many decimal numbers as requested.+  -> Bool+  -- ^ Whether to render a leading @\'+\'@ sign in case the amount is positive.+  -> Maybe Char+  -- ^ Thousands separator for the integer part, if any (i.e., the @\',\'@ in+  -- @1,234.56789@).+  -> Char+  -- ^ Decimal separator (i.e., the @\'.\'@ in @1,234.56789@)+  -> Word8+  -- ^ Number of decimal numbers to render, if any.+  -> Rational+  -- ^ The dense monetary amount to render.+  -> Maybe String+  -- ^ Returns 'Nothing' is the given separators are not acceptable (i.e., they+  -- are digits, or they are equal).+{-# INLINABLE rationalToDecimal #-}+rationalToDecimal a plus ytsep dsep fdigs0 = \r0 -> do+  for_ ytsep $ \tsep ->+     guard (tsep /= dsep && not (Char.isDigit tsep))+  guard (not (Char.isDigit dsep))+  -- this string-fu is not particularly efficient.+  let parts = approximate a (r0 * (10 ^ fdigs0)) :: Integer+      ipart = fromInteger (abs parts) `div` (10 ^ fdigs0) :: Natural+      ftext | ipart == 0 = show (abs parts) :: String+            | otherwise = drop (length (show ipart)) (show (abs parts))+      itext = maybe (show ipart) (renderThousands ipart) ytsep :: String+      fpad0 = List.replicate (fromIntegral fdigs0 - length ftext) '0' :: String+  Just $ mconcat+    [ if | parts < 0 -> "-"+         | plus && parts > 0 -> "+"+         | otherwise -> ""+    , itext+    , if | fdigs0 > 0 -> dsep : ftext <> fpad0+         | otherwise -> ""+    ]+++-- | Render a 'Natural' number with thousand markers.+--+-- @+-- > 'renderThousands' 12045 \',\'+-- \"12,045\"+-- @+renderThousands :: Natural -> Char -> String+{-# INLINABLE renderThousands #-}+renderThousands n0+  | n0 < 1000 = \_ -> show n0+  | otherwise = \c -> List.foldl' (flip mappend) mempty (List.unfoldr (f c) n0)+      where f :: Char -> Natural -> Maybe (String, Natural)+            f c = \x -> case divMod x 1000 of+                        (0, 0) -> Nothing+                        (0, z) -> Just (show z, 0)+                        (y, z) | z <  10   -> Just (c:'0':'0':show z, y)+                               | z < 100   -> Just (c:'0':show z, y)+                               | otherwise -> Just (c:show z, y)++--------------------------------------------------------------------------------+-- Decimal parsing++-- | Parses a decimal representation of a 'Dense'.+--+-- Leading @\'-\'@ and @\'+\'@ characters are considered.+denseFromDecimal+  :: Maybe Char+  -- ^ Thousands separator for the integer part, if any (i.e., the @\',\'@ in+  -- @-1,234.56789@).+  -> Char+  -- ^ Decimal separator (i.e., the @\'.\'@ in @-1,234.56789@)+  -> String+  -- ^ The raw string containing the decimal representation (e.g.,+  -- @"-1,234.56789"@).+  -> Maybe (Dense currency)+denseFromDecimal yst sf = fmap Dense . rationalFromDecimal yst sf++-- | Parses a decimal representation of a 'Discrete'.+--+-- Leading @\'-\'@ and @\'+\'@ characters are considered.+--+-- Notice that parsing will fail unless the entire precision of the decimal+-- number can be represented in the desired @scale@.+discreteFromDecimal+  :: GoodScale scale+  => Maybe Char+  -- ^ Thousands separator for the integer part, if any (i.e., the @\',\'@ in+  -- @-1,234.56789@).+  -> Char+  -- ^ Decimal separator (i.e., the @\'.\'@ in @-1,234.56789@)+  -> String+  -- ^ The raw string containing the decimal representation (e.g.,+  -- @"-1,234.56789"@).+  -> Maybe (Discrete' currency scale)+discreteFromDecimal yst sf = \s -> do+  dns <- denseFromDecimal yst sf s+  case discreteFromDense Truncate dns of+    (x, 0) -> Just x+    _ -> Nothing -- We fail for decimals that don't fit exactly in our scale.++-- | Parses a decimal representation of an 'ExchangeRate'.+exchangeRateFromDecimal+  :: Maybe Char+  -- ^ Thousands separator for the integer part, if any (i.e., the @\',\'@ in+  -- @1,234.56789@).+  -> Char+  -- ^ Decimal separator (i.e., the @\'.\'@ in @1,234.56789@)+  -> String+  -- ^ The raw string containing the decimal representation (e.g.,+  -- @"1,234.56789"@).+  -> Maybe (ExchangeRate src dst)+exchangeRateFromDecimal yst sf = \case+  ('-':_) -> Nothing+  str -> exchangeRate =<< rationalFromDecimal yst sf str++rationalFromDecimal+  :: Maybe Char+  -- ^ Thousands separator for the integer part, if any (i.e., the @\',\'@ in+  -- @-1,234.56789@).+  -> Char+  -- ^ Decimal separator (i.e., the @\'.\'@ in @-1,234.56789@)+  -> String+  -- ^ The raw string containing the decimal representation (e.g.,+  -- @"-1,234.56789"@).+  -> Maybe Rational+rationalFromDecimal yst sf = \s ->+  case ReadP.readP_to_S (rationalFromDecimalP yst sf) s of+    [(x,"")] -> Just x+    _ -> Nothing++-- TODO limit number of digits parsed to prevent DoS+rationalFromDecimalP+  :: Maybe Char+  -- ^ Thousands separator for the integer part, if any (i.e., the @\',\'@ in+  -- @-1,234.56789@).+  --+  -- The separator can't be a digit or control character. If it is, then parsing+  -- will always fail.+  -> Char+  -- ^ Decimal separator (i.e., the @\'.\'@ in @-1,234.56789@).+  --+  -- The separator can't be a digit or control character. If it is, then parsing+  -- will always fail.+  -> ReadP.ReadP Rational+rationalFromDecimalP ytsep dsep = do+   for_ ytsep $ \tsep ->+      guard (tsep /= dsep && not (Char.isDigit tsep))+   guard (not (Char.isDigit dsep))+   sig :: Rational -> Rational <-+     (ReadP.char '-' $> negate) <|>+     (ReadP.char '+' $> id) <|>+     (pure id)+   ipart :: String <- case ytsep of+     Nothing -> ReadP.munch1 Char.isDigit+     Just tsep -> mappend+       <$> (ReadP.count 3 (ReadP.satisfy Char.isDigit) <|>+            ReadP.count 2 (ReadP.satisfy Char.isDigit) <|>+            ReadP.count 1 (ReadP.satisfy Char.isDigit))+       <*> (fmap concat $ ReadP.many+              (ReadP.char tsep *> ReadP.count 3 (ReadP.satisfy Char.isDigit)))+   yfpart :: Maybe String <-+     (ReadP.char dsep *> fmap Just (ReadP.munch1 Char.isDigit) <* ReadP.eof) <|>+     (ReadP.eof $> Nothing)+   pure $! sig $ case yfpart of+     Nothing -> fromInteger (read ipart)+     Just fpart -> read (ipart <> fpart) % (10 ^ length fpart) 
test/Main.hs view
@@ -5,17 +5,22 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeApplications #-}  module Main where  import Control.Category (Category((.), id)) import qualified Data.ByteString.Lazy as BSL-import Data.Maybe (catMaybes, isJust, isNothing)+import qualified Data.Char as Char+import Data.Maybe (catMaybes, isJust, isNothing, fromJust) import Data.Proxy (Proxy(Proxy))-import Data.Ratio (numerator, denominator)+import Data.Ratio ((%), numerator, denominator)+import Data.Word (Word8) import GHC.TypeLits (Nat, Symbol, KnownSymbol, symbolVal) import Prelude hiding ((.), id) import qualified Test.Tasty as Tasty+import Test.Tasty.HUnit ((@?=))+import qualified Test.Tasty.HUnit as HU import qualified Test.Tasty.Runners as Tasty import Test.Tasty.QuickCheck ((===), (==>), (.&&.)) import qualified Test.Tasty.QuickCheck as QC@@ -40,12 +45,19 @@ import qualified Data.Store as Store #endif +#ifdef HAS_vector_space+import qualified Data.AdditiveGroup as AG+import qualified Data.VectorSpace as VS+#endif+ #ifdef HAS_xmlbf import qualified Xmlbf import qualified Data.Text as Text #endif  import qualified Money+import qualified Money.Internal as Money+  (rationalFromDecimal, rationalToDecimal)  -------------------------------------------------------------------------------- @@ -59,46 +71,73 @@   arbitrary = do     let md = Money.mkSomeDiscrete                <$> QC.arbitrary <*> QC.arbitrary <*> QC.arbitrary-    Just x <- QC.suchThat md isJust-    pure x+    fromJust <$> QC.suchThat md isJust   shrink = \x -> Money.withSomeDiscrete x (map Money.toSomeDiscrete . QC.shrink)  instance QC.Arbitrary (Money.Dense currency) where   arbitrary = do-    Just x <- QC.suchThat (Money.dense <$> QC.arbitrary) isJust-    pure x-  shrink = catMaybes . fmap Money.dense . QC.shrink . toRational+     let myd = fmap Money.dense QC.arbitrary+     fromJust <$> QC.suchThat myd isJust+  shrink = catMaybes . map Money.dense . QC.shrink . toRational  instance QC.Arbitrary Money.SomeDense where   arbitrary = do     let md = Money.mkSomeDense <$> QC.arbitrary <*> QC.arbitrary-    Just x <- QC.suchThat md isJust-    pure x+    fromJust <$> QC.suchThat md isJust   shrink = \x -> Money.withSomeDense x (map Money.toSomeDense . QC.shrink)  instance QC.Arbitrary (Money.ExchangeRate src dst) where   arbitrary = do-    Just x <- QC.suchThat (fmap Money.exchangeRate QC.arbitrary) isJust-    pure x-  shrink =-    catMaybes . fmap Money.exchangeRate . QC.shrink . Money.fromExchangeRate+    let myxr = fmap Money.exchangeRate QC.arbitrary+    fromJust <$> QC.suchThat myxr isJust+  shrink = catMaybes . map Money.exchangeRate+         . QC.shrink . Money.exchangeRateToRational  instance QC.Arbitrary Money.SomeExchangeRate where   arbitrary = do     let md = Money.mkSomeExchangeRate                <$> QC.arbitrary <*> QC.arbitrary <*> QC.arbitrary-    Just x <- QC.suchThat md isJust-    pure x+    fromJust <$> QC.suchThat md isJust   shrink = \x ->     Money.withSomeExchangeRate x (map Money.toSomeExchangeRate . QC.shrink) +instance QC.Arbitrary Money.Approximation where+  arbitrary = QC.oneof [ pure Money.Round+                       , pure Money.Floor+                       , pure Money.Ceiling+                       , pure Money.Truncate ]++-- | Generates a valid 'Money.rationalToDecimal' result. Returns the thousand+-- and decimal separators as welland decimal separators as well.+genDecimal :: QC.Gen (String, Maybe Char, Char)+genDecimal = do+  aprox :: Money.Approximation <- QC.arbitrary+  plus :: Bool <- QC.arbitrary+  digs :: Word8 <- QC.arbitrary+  r :: Rational <- (%) <$> QC.arbitrary <*> QC.suchThat QC.arbitrary (/= 0)+  (yts, ds) <- genDecimalSeps+  Just dec <- pure (Money.rationalToDecimal aprox plus yts ds digs r)+  pure (dec, yts, ds)++-- | Generates valid separators for decimal representations (see genDecimal).+genDecimalSeps :: QC.Gen (Maybe Char, Char)+genDecimalSeps = do+  let msep = QC.suchThat QC.arbitrary (not . Char.isDigit)+  ds :: Char <- msep+  yts :: Maybe Char <- genMaybe (QC.suchThat msep (/= ds))+  pure (yts, ds)+++genMaybe :: QC.Gen a -> QC.Gen (Maybe a)+genMaybe m = QC.oneof [pure Nothing, fmap Just m]+ --------------------------------------------------------------------------------  main :: IO () main =  Tasty.defaultMainWithIngredients   [ Tasty.consoleTestReporter   , Tasty.listingTests-  ] tests+  ] (Tasty.localOption (QC.QuickCheckTests 500) tests)  tests :: Tasty.TestTree tests =@@ -106,6 +145,9 @@   [ testCurrencies   , testCurrencyUnits   , testExchange+  , testRationalToDecimal+  , testRationalFromDecimal+  , testDiscreteFromDecimal   ]  testCurrencies :: Tasty.TestTree@@ -127,11 +169,274 @@   , testDiscrete (Proxy :: Proxy "USD") (Proxy :: Proxy "cent")   , testDiscrete (Proxy :: Proxy "USD") (Proxy :: Proxy "dollar")   , testDiscrete (Proxy :: Proxy "VUV") (Proxy :: Proxy "vatu")-  , testDiscrete (Proxy :: Proxy "XAU") (Proxy :: Proxy "micrograin")-  , testDiscrete (Proxy :: Proxy "XAU") (Proxy :: Proxy "milligrain")+  , testDiscrete (Proxy :: Proxy "XAU") (Proxy :: Proxy "gram")   , testDiscrete (Proxy :: Proxy "XAU") (Proxy :: Proxy "grain")   ] ++testRationalToDecimal :: Tasty.TestTree+testRationalToDecimal =+  Tasty.testGroup "rationalToDecimal"+  [ HU.testCase "Round: r1" $ do+       render Money.Round r1 @?=+         [ "1023004567.90"        --  0+         , "1,023,004,567.90"     --  1+         , "+1023004567.90"       --  2+         , "+1,023,004,567.90"    --  3+         , "1023004568"           --  8+         , "1,023,004,568"        --  9+         , "+1023004568"          -- 10+         , "+1,023,004,568"       -- 11+         ]+  , HU.testCase "Round: negate r1" $ do+       render Money.Round (negate r1) @?=+         [ "-1023004567.90"       --  0+         , "-1,023,004,567.90"    --  1+         , "-1023004567.90"       --  2+         , "-1,023,004,567.90"    --  3+         , "-1023004568"          --  8+         , "-1,023,004,568"       --  9+         , "-1023004568"          -- 10+         , "-1,023,004,568"       -- 11+         ]+  , HU.testCase "Round: r2" $ do+       render Money.Round r2 @?=+         [ "1.23"    --  0+         , "1.23"    --  1+         , "+1.23"   --  2+         , "+1.23"   --  3+         , "1"       --  8+         , "1"       --  9+         , "+1"      -- 10+         , "+1"      -- 11+         ]+  , HU.testCase "Round: negate r2" $ do+       render Money.Round (negate r2) @?=+         [ "-1.23"    --  0+         , "-1.23"    --  1+         , "-1.23"    --  2+         , "-1.23"    --  3+         , "-1"       --  8+         , "-1"       --  9+         , "-1"       -- 10+         , "-1"       -- 11+         ]+  , HU.testCase "Round: r3" $ do+       render Money.Round r3 @?=+         [ "0.34"   --  0+         , "0.34"   --  1+         , "+0.34"  --  2+         , "+0.34"  --  3+         , "0"      --  8+         , "0"      --  9+         , "0"      -- 10+         , "0"      -- 11+         ]+  , HU.testCase "Round: negate r3" $ do+       render Money.Round (negate r3) @?=+         [ "-0.34"   --  0+         , "-0.34"   --  1+         , "-0.34"   --  2+         , "-0.34"   --  3+         , "0"       --  8+         , "0"       --  9+         , "0"       -- 10+         , "0"       -- 11+         ]+  , HU.testCase "Floor: r1" $ do+       render Money.Floor r1 @?=+         [ "1023004567.89"        --  0+         , "1,023,004,567.89"     --  1+         , "+1023004567.89"       --  2+         , "+1,023,004,567.89"    --  3+         , "1023004567"           --  8+         , "1,023,004,567"        --  9+         , "+1023004567"          -- 10+         , "+1,023,004,567"       -- 11+         ]+  , HU.testCase "Floor: negate r1" $ do+       render Money.Floor (negate r1) @?=+         [ "-1023004567.90"       --  0+         , "-1,023,004,567.90"    --  1+         , "-1023004567.90"       --  2+         , "-1,023,004,567.90"    --  3+         , "-1023004568"          --  8+         , "-1,023,004,568"       --  9+         , "-1023004568"          -- 10+         , "-1,023,004,568"       -- 11+         ]+  , HU.testCase "Floor: r2" $ do+       render Money.Floor r2 @?=+         [ "1.23"    --  0+         , "1.23"    --  1+         , "+1.23"   --  2+         , "+1.23"   --  3+         , "1"       --  8+         , "1"       --  9+         , "+1"      -- 10+         , "+1"      -- 11+         ]+  , HU.testCase "Floor: negate r2" $ do+       render Money.Floor (negate r2) @?=+         [ "-1.23"    --  0+         , "-1.23"    --  1+         , "-1.23"    --  2+         , "-1.23"    --  3+         , "-2"       --  8+         , "-2"       --  9+         , "-2"       -- 10+         , "-2"       -- 11+         ]+  , HU.testCase "Floor: r3" $ do+       render Money.Floor r3 @?=+         [ "0.34"   --  0+         , "0.34"   --  1+         , "+0.34"  --  2+         , "+0.34"  --  3+         , "0"      --  8+         , "0"      --  9+         , "0"      -- 10+         , "0"      -- 11+         ]+  , HU.testCase "Floor: negate r3" $ do+       render Money.Floor (negate r3) @?=+         [ "-0.35"   --  0+         , "-0.35"   --  1+         , "-0.35"   --  2+         , "-0.35"   --  3+         , "-1"      --  8+         , "-1"      --  9+         , "-1"      -- 10+         , "-1"      -- 11+         ]+  , HU.testCase "Ceiling: r1" $ do+       render Money.Ceiling r1 @?=+         [ "1023004567.90"        --  0+         , "1,023,004,567.90"     --  1+         , "+1023004567.90"       --  2+         , "+1,023,004,567.90"    --  3+         , "1023004568"           --  8+         , "1,023,004,568"        --  9+         , "+1023004568"          -- 10+         , "+1,023,004,568"       -- 11+         ]+  , HU.testCase "Ceiling: negate r1" $ do+       render Money.Ceiling (negate r1) @?=+         [ "-1023004567.89"       --  0+         , "-1,023,004,567.89"    --  1+         , "-1023004567.89"       --  2+         , "-1,023,004,567.89"    --  3+         , "-1023004567"          --  8+         , "-1,023,004,567"       --  9+         , "-1023004567"          -- 10+         , "-1,023,004,567"       -- 11+         ]+  , HU.testCase "Ceiling: r2" $ do+       render Money.Ceiling r2 @?=+         [ "1.23"    --  0+         , "1.23"    --  1+         , "+1.23"   --  2+         , "+1.23"   --  3+         , "2"       --  8+         , "2"       --  9+         , "+2"      -- 10+         , "+2"      -- 11+         ]+  , HU.testCase "Ceiling: negate r2" $ do+       render Money.Ceiling (negate r2) @?=+         [ "-1.23"    --  0+         , "-1.23"    --  1+         , "-1.23"    --  2+         , "-1.23"    --  3+         , "-1"       --  8+         , "-1"       --  9+         , "-1"       -- 10+         , "-1"       -- 11+         ]+  , HU.testCase "Ceiling: r3" $ do+       render Money.Ceiling r3 @?=+         [ "0.35"   --  0+         , "0.35"   --  1+         , "+0.35"  --  2+         , "+0.35"  --  3+         , "1"      --  8+         , "1"      --  9+         , "+1"     -- 10+         , "+1"     -- 11+         ]+  , HU.testCase "Ceiling: negate r3" $ do+       render Money.Ceiling (negate r3) @?=+         [ "-0.34"   --  0+         , "-0.34"   --  1+         , "-0.34"   --  2+         , "-0.34"   --  3+         , "0"       --  8+         , "0"       --  9+         , "0"       -- 10+         , "0"       -- 11+         ]++  , HU.testCase "Truncate: r1" $ do+      render Money.Truncate r1 @?= render Money.Floor r1++  , HU.testCase "Truncate: negate r1" $ do+      render Money.Truncate (negate r1) @?= render Money.Ceiling (negate r1)++  , HU.testCase "Truncate: r2" $ do+      render Money.Truncate r2 @?= render Money.Floor r2++  , HU.testCase "Truncate: negate r2" $ do+      render Money.Truncate (negate r2) @?= render Money.Ceiling (negate r2)++  , HU.testCase "Truncate: r3" $ do+      render Money.Truncate r3 @?= render Money.Floor r3++  , HU.testCase "Truncate: negate r3" $ do+      render Money.Truncate (negate r3) @?= render Money.Ceiling (negate r3)+  ]+  where+    r1 :: Rational = 1023004567895 % 1000+    r2 :: Rational = 123 % 100+    r3 :: Rational = 345 % 1000++    render :: Money.Approximation -> Rational -> [String]+    render a r =+      [ fromJust $ Money.rationalToDecimal a False Nothing    '.' 2 r  --  0+      , fromJust $ Money.rationalToDecimal a False (Just ',') '.' 2 r  --  1+      , fromJust $ Money.rationalToDecimal a True  Nothing    '.' 2 r  --  2+      , fromJust $ Money.rationalToDecimal a True  (Just ',') '.' 2 r  --  3+      , fromJust $ Money.rationalToDecimal a False Nothing    '.' 0 r  --  8+      , fromJust $ Money.rationalToDecimal a False (Just ',') '.' 0 r  --  9+      , fromJust $ Money.rationalToDecimal a True  Nothing    '.' 0 r  -- 10+      , fromJust $ Money.rationalToDecimal a True  (Just ',') '.' 0 r  -- 11+      ]++testRationalFromDecimal :: Tasty.TestTree+testRationalFromDecimal =+  Tasty.testGroup "rationalFromDecimal"+  [ QC.testProperty "Unsupported separators" $+      let mbadsep :: QC.Gen Char = QC.suchThat QC.arbitrary Char.isDigit+          mgoodsep :: QC.Gen Char = QC.suchThat QC.arbitrary (not . Char.isDigit)+      in QC.forAll ((,,) <$> mbadsep <*> mbadsep <*> mgoodsep) $+           \(s1 :: Char, s2 :: Char, s3 :: Char) ->+              let f = Money.rationalFromDecimal+              in (f Nothing s1 (error "untouched") === Nothing) .&&.+                 (f (Just s1) s2 (error "untouched") === Nothing) .&&.+                 (f (Just s1) s1 (error "untouched") === Nothing) .&&.+                 (f (Just s3) s3 (error "untouched") === Nothing)++  , QC.testProperty "Lossy roundtrip" $+      -- We check that the roundtrip results in a close amount with a fractional+      -- difference of up to one.+      let gen = (,) <$> genDecimalSeps <*> QC.arbitrary+      in QC.forAll gen $ \( (yst :: Maybe Char, sd :: Char)+                       , (r :: Rational, plus :: Bool, digs :: Word8,+                          aprox :: Money.Approximation) ) ->+           let Just dec = Money.rationalToDecimal aprox plus yst sd digs r+               Just r' = Money.rationalFromDecimal yst sd dec+           in 1 > abs (abs r - abs r')+  ]+ testDense   :: forall currency   .  KnownSymbol currency@@ -142,16 +447,20 @@   [ QC.testProperty "read . show == id" $       QC.forAll QC.arbitrary $ \(x :: Money.Dense currency) ->          x === read (show x)+   , QC.testProperty "read . show . Just == Just " $       QC.forAll QC.arbitrary $ \(x :: Money.Dense currency) ->          Just x === read (show (Just x))+   , QC.testProperty "fromSomeDense . someDense == Just" $       QC.forAll QC.arbitrary $ \(x :: Money.Dense currency) ->          Just x === Money.fromSomeDense (Money.toSomeDense x)+   , QC.testProperty "fromSomeDense works only for same currency" $       QC.forAll QC.arbitrary $ \(dr :: Money.SomeDense) ->         (Money.someDenseCurrency dr /= symbolVal pc)            ==> isNothing (Money.fromSomeDense dr :: Maybe (Money.Dense currency))+   , QC.testProperty "withSomeDense" $       QC.forAll QC.arbitrary $ \(x :: Money.Dense currency) ->         let dr = Money.toSomeDense x@@ -159,6 +468,45 @@              (show x, dr, Money.toSomeDense (x + 1))                 === (show x', Money.toSomeDense x', Money.toSomeDense (x' + 1)) +  , QC.testProperty "denseCurrency" $+      QC.forAll QC.arbitrary $ \(x :: Money.Dense currency) ->+        Money.denseCurrency x === symbolVal pc++  , QC.testProperty "denseToDecimal: Same as rationalToDecimal" $+      let gen = (,) <$> genDecimalSeps <*> QC.arbitrary+      in QC.forAll gen $ \( (yst :: Maybe Char, sd :: Char)+                          , (dns :: Money.Dense currency, plus :: Bool,+                             digs :: Word8, aprox :: Money.Approximation) ) ->+            let ydnsd1 = Money.denseToDecimal aprox plus yst sd digs (Proxy :: Proxy '(1,1)) dns+                ydnsd100 = Money.denseToDecimal aprox plus yst sd digs (Proxy :: Proxy '(100,1)) dns+                yrd1 = Money.rationalToDecimal aprox plus yst sd digs (toRational dns)+                yrd100 = Money.rationalToDecimal aprox plus yst sd digs (toRational dns * 100)+            in (ydnsd1 === yrd1) .&&. (ydnsd100 === yrd100)++  , QC.testProperty "denseFromDecimal: Same as rationalFromDecimal" $+      QC.forAll genDecimal $ \(dec :: String, yts :: Maybe Char, ds :: Char) ->+         let Just r = Money.rationalFromDecimal yts ds dec+             Just dns = Money.denseFromDecimal yts ds dec+         in r === toRational (dns :: Money.Dense currency)++#ifdef HAS_vector_space+  , HU.testCase "AdditiveGroup: zeroV" $+      (AG.zeroV :: Money.Dense currency) @?= Money.dense' (0%1)+  , QC.testProperty "AdditiveGroup: negateV" $+      QC.forAll QC.arbitrary $ \(x :: Money.Dense currency) ->+         AG.negateV x === negate x+  , QC.testProperty "AdditiveGroup: ^+^" $+      QC.forAll QC.arbitrary $ \(x :: Money.Dense currency, y) ->+         x AG.^+^ y === x + y+  , QC.testProperty "AdditiveGroup: ^-^" $+      QC.forAll QC.arbitrary $ \(x :: Money.Dense currency, y) ->+         x AG.^-^ y === x - y+  , QC.testProperty "VectorSpace: *^" $+      QC.forAll QC.arbitrary $ \(x :: Money.Dense currency, y) ->+         (toRational x VS.*^ y === x * y) .&&.+         (toRational y VS.*^ x === x * y)+#endif+ #ifdef HAS_aeson   , QC.testProperty "Aeson encoding roundtrip" $       QC.forAll QC.arbitrary $ \(x :: Money.Dense currency) ->@@ -291,6 +639,7 @@   , testExchangeRate (Proxy :: Proxy "XAU") (Proxy :: Proxy "XAU")   ] + testDiscrete   :: forall (currency :: Symbol) (unit :: Symbol)   .  ( Money.GoodScale (Money.Scale currency unit)@@ -327,6 +676,28 @@                    === (show x', Money.toSomeDiscrete x', Money.toSomeDiscrete (x' + 1))            ) :: QC.Property +  , QC.testProperty "discreteCurrency" $+      QC.forAll QC.arbitrary $ \(x :: Money.Discrete currency unit) ->+        Money.discreteCurrency x === symbolVal pc++#ifdef HAS_vector_space+  , HU.testCase "AdditiveGroup: zeroV" $+      (AG.zeroV :: Money.Discrete currency unit) @?= Money.discrete 0+  , QC.testProperty "AdditiveGroup: negateV" $+      QC.forAll QC.arbitrary $ \(x :: Money.Discrete currency unit) ->+         AG.negateV x === negate x+  , QC.testProperty "AdditiveGroup: ^+^" $+      QC.forAll QC.arbitrary $ \(x :: Money.Discrete currency unit, y) ->+         x AG.^+^ y === x + y+  , QC.testProperty "AdditiveGroup: ^-^" $+      QC.forAll QC.arbitrary $ \(x :: Money.Discrete currency unit, y) ->+         x AG.^-^ y === x - y+  , QC.testProperty "VectorSpace: *^" $+      QC.forAll QC.arbitrary $ \(x :: Money.Discrete currency unit, y) ->+         (toInteger x VS.*^ y === x * y) .&&.+         (toInteger y VS.*^ x === x * y)+#endif+ #ifdef HAS_aeson   , QC.testProperty "Aeson encoding roundtrip" $       QC.forAll QC.arbitrary $ \(x :: Money.Discrete currency unit) ->@@ -456,14 +827,14 @@          xr === xr . id   , QC.testProperty "Category: composition with inverse" $       QC.forAll QC.arbitrary $ \(xr1 :: Money.ExchangeRate src dst) ->-         (1 === Money.fromExchangeRate (xr1 . Money.flipExchangeRate xr1)) .&&.-         (1 === Money.fromExchangeRate (Money.flipExchangeRate xr1 . xr1))+         (1 === Money.exchangeRateToRational (xr1 . Money.exchangeRateRecip xr1)) .&&.+         (1 === Money.exchangeRateToRational (Money.exchangeRateRecip xr1 . xr1))   , QC.testProperty "Category: composition with other" $       QC.forAll QC.arbitrary $ \(xr1 :: Money.ExchangeRate src dst,                                  xr2 :: Money.ExchangeRate dst src) ->-         let a = Money.fromExchangeRate xr1 * Money.fromExchangeRate xr2-         in (a === Money.fromExchangeRate (xr1 . xr2)) .&&.-            (a === Money.fromExchangeRate (xr2 . xr1))+         let a = Money.exchangeRateToRational xr1 * Money.exchangeRateToRational xr2+         in (a === Money.exchangeRateToRational (xr1 . xr2)) .&&.+            (a === Money.exchangeRateToRational (xr2 . xr1))    , QC.testProperty "read . show == id" $       QC.forAll QC.arbitrary $ \(xr :: Money.ExchangeRate src dst) ->@@ -473,25 +844,25 @@          Just xr === read (show (Just xr))   , QC.testProperty "flipExchangeRate . flipExchangeRate == id" $       QC.forAll QC.arbitrary $ \(xr :: Money.ExchangeRate src dst) ->-         let xr' = Money.flipExchangeRate xr-         in (Money.fromExchangeRate xr /= Money.fromExchangeRate xr')-               ==> (xr === Money.flipExchangeRate xr')+         let xr' = Money.exchangeRateRecip xr+         in (Money.exchangeRateToRational xr /= Money.exchangeRateToRational xr')+               ==> (xr === Money.exchangeRateRecip xr')   , QC.testProperty "exchange (flipExchangeRate x) . exchange x == id" $       QC.forAll QC.arbitrary $          \( c0 :: Money.Dense src           , xr :: Money.ExchangeRate src dst-          ) -> c0 === Money.exchange (Money.flipExchangeRate xr)+          ) -> c0 === Money.exchange (Money.exchangeRateRecip xr)                                      (Money.exchange xr c0)-  , QC.testProperty "x == 1 ===> exchange x == id" $+  , QC.testProperty "x == 1 ==> exchange x == id" $       QC.forAll QC.arbitrary $          \( c0 :: Money.Dense src           ) -> let Just xr = Money.exchangeRate 1                in toRational c0 === toRational (Money.exchange xr c0)-  , QC.testProperty "x /= 1 ===> exchange x /= id" $+  , QC.testProperty "x /= 1 ==> exchange x /= id" $       QC.forAll QC.arbitrary $          \( c0 :: Money.Dense src           , xr :: Money.ExchangeRate src dst-          ) -> (Money.fromExchangeRate xr /= 1)+          ) -> (Money.exchangeRateToRational xr /= 1 && toRational c0 /= 0)                   ==> (toRational c0 /= toRational (Money.exchange xr c0))   , QC.testProperty "fromSomeExchangeRate . someExchangeRate == Just" $       QC.forAll QC.arbitrary $ \(x :: Money.ExchangeRate src dst) ->@@ -508,6 +879,23 @@         in Money.withSomeExchangeRate dr $ \x' ->              (show x, dr) === (show x', Money.toSomeExchangeRate x') +  , QC.testProperty "exchangeRateToDecimal: Same as rationalToDecimal" $+      let gen = (,) <$> genDecimalSeps <*> QC.arbitrary+      in QC.forAll gen $ \( (yst :: Maybe Char, sd :: Char)+                          , (xr :: Money.ExchangeRate src dst, digs :: Word8,+                             aprox :: Money.Approximation ) ) ->+           let xrd = Money.exchangeRateToDecimal aprox yst sd digs xr+               rd = Money.rationalToDecimal aprox False yst sd digs+                       (Money.exchangeRateToRational xr)+           in xrd === rd++  , QC.testProperty "exchangeRateFromDecimal: Same as rationalFromDecimal" $+      QC.forAll genDecimal $ \(dec :: String, yts :: Maybe Char, ds :: Char) ->+         let Just r = Money.rationalFromDecimal yts ds dec+             yxr = Money.exchangeRateFromDecimal yts ds dec+                      :: Maybe (Money.ExchangeRate src dst)+         in (r > 0) ==> (Just r === fmap Money.exchangeRateToRational yxr)+ #ifdef HAS_aeson   , QC.testProperty "Aeson encoding roundtrip" $       QC.forAll QC.arbitrary $ \(x :: Money.ExchangeRate src dst) ->@@ -620,6 +1008,59 @@ #endif   ] ++testDiscreteFromDecimal :: Tasty.TestTree+testDiscreteFromDecimal =+  Tasty.testGroup "discreteFromDecimal"+  [ HU.testCase "Too large" $ do+      Money.discreteFromDecimal Nothing '.' "0.053"+        @?= (Nothing :: Maybe (Money.Discrete "USD" "cent"))+      Money.discreteFromDecimal (Just ',') '.' "0.253"+        @?= (Nothing :: Maybe (Money.Discrete "USD" "cent"))++  , HU.testCase "USD cent, small, zero" $ do+      let dis = 0 :: Money.Discrete "USD" "cent"+          f = Money.discreteFromDecimal+      f Nothing '.' "0" @?= Just dis+      f Nothing '.' "+0" @?= Just dis+      f Nothing '.' "-0" @?= Just dis+      f (Just ',') '.' "0" @?= Just dis+      f (Just ',') '.' "+0" @?= Just dis+      f (Just ',') '.' "-0" @?= Just dis++  , HU.testCase "USD cent, small, positive" $ do+      let dis = 25 :: Money.Discrete "USD" "cent"+          f = Money.discreteFromDecimal+      f Nothing '.' "0.25" @?= Just dis+      f Nothing '.' "+0.25" @?= Just dis+      f (Just ',') '.' "0.25" @?= Just dis+      f (Just ',') '.' "+0.25" @?= Just dis++  , HU.testCase "USD cent, small, negative" $ do+      let dis = -25 :: Money.Discrete "USD" "cent"+          f = Money.discreteFromDecimal+      f Nothing '.' "-0.25" @?= Just dis+      f Nothing '.' "-0.25" @?= Just dis+      f (Just ',') '.' "-0.25" @?= Just dis+      f (Just ',') '.' "-0.25" @?= Just dis++  , HU.testCase "USD cent, big, positive" $ do+      let dis = 102300456789 :: Money.Discrete "USD" "cent"+          f = Money.discreteFromDecimal+      f Nothing '.' "1023004567.89" @?= Just dis+      f Nothing '.' "+1023004567.89" @?= Just dis+      f (Just ',') '.' "1,023,004,567.89" @?= Just dis+      f (Just ',') '.' "+1,023,004,567.89" @?= Just dis++  , HU.testCase "USD cent, big, negative" $ do+      let dis = -102300456789 :: Money.Discrete "USD" "cent"+          f = Money.discreteFromDecimal+      f Nothing '.' "-1023004567.89" @?= Just dis+      f Nothing '.' "-1023004567.89" @?= Just dis+      f (Just ',') '.' "-1,023,004,567.89" @?= Just dis+      f (Just ',') '.' "-1,023,004,567.89" @?= Just dis+  ]+ testRounding   :: forall (currency :: Symbol) (unit :: Symbol)   .  (Money.GoodScale (Money.Scale currency unit), KnownSymbol currency)@@ -628,26 +1069,26 @@   -> Tasty.TestTree testRounding _ _ =     Tasty.testGroup "Rounding"-    [ QC.testProperty "floor"    $ QC.forAll QC.arbitrary (g Money.floor)-    , QC.testProperty "ceiling"  $ QC.forAll QC.arbitrary (g Money.ceiling)-    , QC.testProperty "round"    $ QC.forAll QC.arbitrary (g Money.round)-    , QC.testProperty "truncate" $ QC.forAll QC.arbitrary (g Money.truncate)-    , QC.testProperty "floor no reminder"    $ QC.forAll QC.arbitrary (h Money.floor)-    , QC.testProperty "ceiling no reminder"  $ QC.forAll QC.arbitrary (h Money.ceiling)-    , QC.testProperty "round no reminder"    $ QC.forAll QC.arbitrary (h Money.round)-    , QC.testProperty "truncate no reminder" $ QC.forAll QC.arbitrary (h Money.truncate)+    [ QC.testProperty "floor"    $ QC.forAll QC.arbitrary (g (Money.discreteFromDense Money.Floor))+    , QC.testProperty "ceiling"  $ QC.forAll QC.arbitrary (g (Money.discreteFromDense Money.Ceiling))+    , QC.testProperty "round"    $ QC.forAll QC.arbitrary (g (Money.discreteFromDense Money.Round))+    , QC.testProperty "truncate" $ QC.forAll QC.arbitrary (g (Money.discreteFromDense Money.Truncate))+    , QC.testProperty "floor no reminder"    $ QC.forAll QC.arbitrary (h (Money.discreteFromDense Money.Floor))+    , QC.testProperty "ceiling no reminder"  $ QC.forAll QC.arbitrary (h (Money.discreteFromDense Money.Ceiling))+    , QC.testProperty "round no reminder"    $ QC.forAll QC.arbitrary (h (Money.discreteFromDense Money.Round))+    , QC.testProperty "truncate no reminder" $ QC.forAll QC.arbitrary (h (Money.discreteFromDense Money.Truncate))     ]   where     g :: (Money.Dense currency -> (Money.Discrete' currency (Money.Scale currency unit), Money.Dense currency))       -> Money.Dense currency       -> QC.Property-    g f = \x -> x === case f x of (y, z) -> Money.fromDiscrete y + z+    g f = \x -> x === case f x of (y, z) -> Money.denseFromDiscrete y + z      h :: (Money.Dense currency -> (Money.Discrete' currency (Money.Scale currency unit), Money.Dense currency))       -> Money.Discrete currency unit       -> QC.Property-    h f = \x -> (Money.fromDiscrete x) === case f (Money.fromDiscrete x) of-      (y, 0) -> Money.fromDiscrete y+    h f = \x -> (Money.denseFromDiscrete x) === case f (Money.denseFromDiscrete x) of+      (y, 0) -> Money.denseFromDiscrete y       (_, _) -> error "testRounding.h: unexpected"  hush :: Either a b -> Maybe b