{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# 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 #-}
module Money.Internal
(
Dense
, denseCurrency
, dense
, dense'
, denseFromDiscrete
, denseFromDecimal
, denseToDecimal
, Discrete
, Discrete'
, discrete
, discreteCurrency
, discreteFromDense
, discreteFromDecimal
, Scale
, GoodScale
, ErrScaleNonCanonical
, scale
, ExchangeRate
, exchangeRate
, exchange
, exchangeRateFromDecimal
, exchangeRateToDecimal
, exchangeRateToRational
, exchangeRateRecip
, SomeDense
, toSomeDense
, mkSomeDense
, fromSomeDense
, withSomeDense
, someDenseCurrency
, someDenseAmount
, SomeDiscrete
, toSomeDiscrete
, mkSomeDiscrete
, fromSomeDiscrete
, withSomeDiscrete
, someDiscreteCurrency
, someDiscreteScale
, someDiscreteAmount
, SomeExchangeRate
, toSomeExchangeRate
, mkSomeExchangeRate
, fromSomeExchangeRate
, withSomeExchangeRate
, someExchangeRateSrcCurrency
, someExchangeRateDstCurrency
, someExchangeRateRate
, 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
newtype Dense (currency :: Symbol) = Dense Rational
deriving (Eq, Ord, Real, GHC.Generic)
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
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
dense :: Rational -> Maybe (Dense currency)
dense = \r ->
if denominator r /= 0
then Just (Dense r)
else Nothing
{-# INLINE dense #-}
dense' :: Rational -> Dense currency
dense' = \r ->
if denominator r /= 0
then Dense r
else error "dense': malformed Rational given (denominator is zero)."
{-# INLINABLE dense' #-}
denseCurrency :: KnownSymbol currency => Dense currency -> String
denseCurrency = symbolVal
{-# INLINE denseCurrency #-}
type Discrete (currency :: Symbol) (unit :: Symbol)
= Discrete' currency (Scale currency unit)
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)
deriving instance GoodScale scale => Num (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), " "
]))
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
discrete :: GoodScale scale => Integer -> Discrete' currency scale
discrete = Discrete
{-# INLINE discrete #-}
denseFromDiscrete
:: GoodScale scale
=> Discrete' currency scale
-> Dense currency
denseFromDiscrete = \c@(Discrete i) -> Dense (fromInteger i / scale c)
{-# INLINE denseFromDiscrete #-}
discreteCurrency
:: forall currency scale
. (KnownSymbol currency, GoodScale scale)
=> Discrete' currency scale
-> String
discreteCurrency = \_ -> symbolVal (Proxy :: Proxy currency)
{-# INLINE discreteCurrency #-}
data Approximation
= Round
| Floor
| Ceiling
| Truncate
deriving (Eq, Ord, Show, Read, GHC.Generic)
approximate :: Approximation -> Rational -> Integer
{-# INLINE approximate #-}
approximate = \case
Round -> round
Floor -> floor
Ceiling -> ceiling
Truncate -> truncate
discreteFromDense
:: forall currency scale
. GoodScale scale
=> Approximation
-> 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 #-}
type family Scale (currency :: Symbol) (unit :: Symbol) :: (Nat, Nat)
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." )
type GoodScale (scale :: (Nat, Nat))
= ( CmpNat 0 (Fst scale) ~ 'LT
, CmpNat 0 (Snd scale) ~ 'LT
, KnownNat (Fst scale)
, KnownNat (Snd scale)
)
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 #-}
scale :: forall proxy scale. GoodScale scale => proxy scale -> Rational
scale = \_ -> natVal (Proxy :: Proxy (Fst scale)) %
natVal (Proxy :: Proxy (Snd scale))
{-# INLINE scale #-}
newtype ExchangeRate (src :: Symbol) (dst :: Symbol) = ExchangeRate Rational
deriving (Eq, Ord, GHC.Generic)
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
exchangeRateToRational :: ExchangeRate src dst -> Rational
exchangeRateToRational = \(ExchangeRate r0) -> r0
{-# INLINE exchangeRateToRational #-}
exchangeRate :: Rational -> Maybe (ExchangeRate src dst)
exchangeRate = \r ->
if denominator r /= 0 && r > 0
then Just (ExchangeRate r)
else Nothing
{-# INLINE exchangeRate #-}
exchangeRateRecip :: ExchangeRate a b -> ExchangeRate b a
exchangeRateRecip = \(ExchangeRate x) ->
ExchangeRate (1 / x)
{-# INLINE exchangeRateRecip #-}
exchange :: ExchangeRate src dst -> Dense src -> Dense dst
exchange (ExchangeRate r) = \(Dense s) -> Dense (r * s)
{-# INLINE exchange #-}
data SomeDense = SomeDense
{ _someDenseCurrency :: !String
, _someDenseAmount :: !Rational
} deriving (Eq, Show, GHC.Generic)
deriving instance Ord SomeDense
someDenseCurrency :: SomeDense -> String
someDenseCurrency = _someDenseCurrency
{-# INLINE someDenseCurrency #-}
someDenseAmount :: SomeDense -> Rational
someDenseAmount = _someDenseAmount
{-# INLINE someDenseAmount #-}
mkSomeDense
:: String
-> Rational
-> Maybe SomeDense
mkSomeDense = \c r ->
if (denominator r /= 0)
then Just (SomeDense c r)
else Nothing
{-# INLINABLE mkSomeDense #-}
toSomeDense :: KnownSymbol currency => Dense currency -> SomeDense
toSomeDense = \(Dense r0 :: Dense currency) ->
let c = symbolVal (Proxy :: Proxy currency)
in SomeDense c r0
{-# INLINE toSomeDense #-}
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 #-}
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 #-}
data SomeDiscrete = SomeDiscrete
{ _someDiscreteCurrency :: !String
, _someDiscreteScale :: !Rational
, _someDiscreteAmount :: !Integer
} deriving (Eq, Show, GHC.Generic)
deriving instance Ord SomeDiscrete
someDiscreteCurrency :: SomeDiscrete -> String
someDiscreteCurrency = _someDiscreteCurrency
{-# INLINE someDiscreteCurrency #-}
someDiscreteScale :: SomeDiscrete -> Rational
someDiscreteScale = _someDiscreteScale
{-# INLINE someDiscreteScale #-}
someDiscreteAmount :: SomeDiscrete -> Integer
someDiscreteAmount = _someDiscreteAmount
{-# INLINE someDiscreteAmount #-}
mkSomeDiscrete
:: String
-> Rational
-> Integer
-> Maybe SomeDiscrete
mkSomeDiscrete = \c r a ->
if (denominator r /= 0) && (r > 0)
then Just (SomeDiscrete c r a)
else Nothing
{-# INLINABLE mkSomeDiscrete #-}
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 #-}
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 #-}
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 #-}
data SomeExchangeRate = SomeExchangeRate
{ _someExchangeRateSrcCurrency :: !String
, _someExchangeRateDstCurrency :: !String
, _someExchangeRateRate :: !Rational
} deriving (Eq, Show, GHC.Generic)
deriving instance Ord SomeExchangeRate
someExchangeRateSrcCurrency :: SomeExchangeRate -> String
someExchangeRateSrcCurrency = _someExchangeRateSrcCurrency
{-# INLINE someExchangeRateSrcCurrency #-}
someExchangeRateDstCurrency :: SomeExchangeRate -> String
someExchangeRateDstCurrency = _someExchangeRateDstCurrency
{-# INLINE someExchangeRateDstCurrency #-}
someExchangeRateRate :: SomeExchangeRate -> Rational
someExchangeRateRate = _someExchangeRateRate
{-# INLINE someExchangeRateRate #-}
mkSomeExchangeRate
:: String
-> String
-> Rational
-> Maybe SomeExchangeRate
mkSomeExchangeRate = \src dst r ->
if (denominator r /= 0) && (r > 0)
then Just (SomeExchangeRate src dst r)
else Nothing
{-# INLINABLE mkSomeExchangeRate #-}
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 #-}
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 #-}
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 #-}
type family Fst (ab :: (ka, kb)) :: ka where Fst '(a,b) = a
type family Snd (ab :: (ka, kb)) :: ka where Snd '(a,b) = b
#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 (^-^) #-}
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
#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
#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
#ifdef HAS_cereal
instance (KnownSymbol currency) => Cereal.Serialize (Dense currency) where
put = Cereal.put . toSomeDense
get = maybe empty pure =<< fmap fromSomeDense Cereal.get
instance
( KnownSymbol currency, GoodScale scale
) => Cereal.Serialize (Discrete' currency scale) where
put = Cereal.put . toSomeDiscrete
get = maybe empty pure =<< fmap fromSomeDiscrete Cereal.get
instance
( KnownSymbol src, KnownSymbol dst
) => Cereal.Serialize (ExchangeRate src dst) where
put = Cereal.put . toSomeExchangeRate
get = maybe empty pure =<< fmap fromSomeExchangeRate Cereal.get
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))
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)
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
#ifdef HAS_binary
instance (KnownSymbol currency) => Binary.Binary (Dense currency) where
put = Binary.put . toSomeDense
get = maybe empty pure =<< fmap fromSomeDense Binary.get
instance
( KnownSymbol currency, GoodScale scale
) => Binary.Binary (Discrete' currency scale) where
put = Binary.put . toSomeDiscrete
get = maybe empty pure =<< fmap fromSomeDiscrete Binary.get
instance
( KnownSymbol src, KnownSymbol dst
) => Binary.Binary (ExchangeRate src dst) where
put = Binary.put . toSomeExchangeRate
get = maybe empty pure =<< fmap fromSomeExchangeRate Binary.get
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))
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)
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
#ifdef HAS_serialise
instance (KnownSymbol currency) => Ser.Serialise (Dense currency) where
encode = Ser.encode . toSomeDense
decode = maybe (fail "Dense") pure =<< fmap fromSomeDense Ser.decode
instance
( KnownSymbol currency, GoodScale scale
) => Ser.Serialise (Discrete' currency scale) where
encode = Ser.encode . toSomeDiscrete
decode = maybe (fail "Discrete'") pure =<< fmap fromSomeDiscrete Ser.decode
instance
( KnownSymbol src, KnownSymbol dst
) => Ser.Serialise (ExchangeRate src dst) where
encode = Ser.encode . toSomeExchangeRate
decode = maybe (fail "ExchangeRate") pure
=<< fmap fromSomeExchangeRate Ser.decode
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))
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)
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
#ifdef HAS_aeson
instance KnownSymbol currency => Ae.ToJSON (Dense currency) where
toJSON = Ae.toJSON . toSomeDense
instance KnownSymbol currency => Ae.FromJSON (Dense currency) where
parseJSON = maybe empty pure <=< fmap fromSomeDense . Ae.parseJSON
instance Ae.ToJSON SomeDense where
toJSON = \(SomeDense c r) ->
Ae.toJSON (c, numerator r, denominator r)
instance Ae.FromJSON SomeDense where
parseJSON = \v -> do
(c, n, d) <- Ae.parseJSON v <|> do
("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))
instance
( KnownSymbol currency, GoodScale scale
) => Ae.ToJSON (Discrete' currency scale) where
toJSON = Ae.toJSON . toSomeDiscrete
instance
( KnownSymbol currency, GoodScale scale
) => Ae.FromJSON (Discrete' currency scale) where
parseJSON = maybe empty pure <=< fmap fromSomeDiscrete . Ae.parseJSON
instance Ae.ToJSON SomeDiscrete where
toJSON = \(SomeDiscrete c r a) ->
Ae.toJSON (c, numerator r, denominator r, a)
instance Ae.FromJSON SomeDiscrete where
parseJSON = \v -> do
(c, n, d, a) <- Ae.parseJSON v <|> do
("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)
instance
( KnownSymbol src, KnownSymbol dst
) => Ae.ToJSON (ExchangeRate src dst) where
toJSON = Ae.toJSON . toSomeExchangeRate
instance
( KnownSymbol src, KnownSymbol dst
) => Ae.FromJSON (ExchangeRate src dst) where
parseJSON = maybe empty pure <=< fmap fromSomeExchangeRate . Ae.parseJSON
instance Ae.ToJSON SomeExchangeRate where
toJSON = \(SomeExchangeRate src dst r) ->
Ae.toJSON (src, dst, numerator r, denominator r)
instance Ae.FromJSON SomeExchangeRate where
parseJSON = \v -> do
(src, dst, n, d) <- Ae.parseJSON v <|> do
("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
#ifdef HAS_xmlbf
instance KnownSymbol currency => Xmlbf.ToXml (Dense currency) where
toXml = Xmlbf.toXml . toSomeDense
instance KnownSymbol currency => Xmlbf.FromXml (Dense currency) where
fromXml = maybe empty pure =<< fmap fromSomeDense Xmlbf.fromXml
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]
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))
instance
( KnownSymbol currency, GoodScale scale
) => Xmlbf.ToXml (Discrete' currency scale) where
toXml = Xmlbf.toXml . toSomeDiscrete
instance
( KnownSymbol currency, GoodScale scale
) => Xmlbf.FromXml (Discrete' currency scale) where
fromXml = maybe empty pure =<< fmap fromSomeDiscrete Xmlbf.fromXml
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]
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)
instance
( KnownSymbol src, KnownSymbol dst
) => Xmlbf.ToXml (ExchangeRate src dst) where
toXml = Xmlbf.toXml . toSomeExchangeRate
instance
( KnownSymbol src, KnownSymbol dst
) => Xmlbf.FromXml (ExchangeRate src dst) where
fromXml = maybe empty pure =<< fmap fromSomeExchangeRate Xmlbf.fromXml
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]
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
#ifdef HAS_store
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
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))
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
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)
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
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
denseToDecimal
:: GoodScale scale
=> Approximation
-> Bool
-> Maybe Char
-> Char
-> Word8
-> Proxy scale
-> Dense currency
-> Maybe String
{-# INLINABLE denseToDecimal #-}
denseToDecimal a plus ytsep dsep fdigs0 ps = \(Dense r0) ->
rationalToDecimal a plus ytsep dsep fdigs0 (scale ps * r0)
exchangeRateToDecimal
:: Approximation
-> Maybe Char
-> Char
-> Word8
-> ExchangeRate src dst
-> Maybe String
{-# INLINABLE exchangeRateToDecimal #-}
exchangeRateToDecimal a ytsep dsep fdigs0 = \(ExchangeRate r0) ->
rationalToDecimal a False ytsep dsep fdigs0 r0
rationalToDecimal
:: Approximation
-> Bool
-> Maybe Char
-> Char
-> Word8
-> Rational
-> Maybe String
{-# 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))
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 -> ""
]
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)
denseFromDecimal
:: Maybe Char
-> Char
-> String
-> Maybe (Dense currency)
denseFromDecimal yst sf = fmap Dense . rationalFromDecimal yst sf
discreteFromDecimal
:: GoodScale scale
=> Maybe Char
-> Char
-> String
-> Maybe (Discrete' currency scale)
discreteFromDecimal yst sf = \s -> do
dns <- denseFromDecimal yst sf s
case discreteFromDense Truncate dns of
(x, 0) -> Just x
_ -> Nothing
exchangeRateFromDecimal
:: Maybe Char
-> Char
-> String
-> Maybe (ExchangeRate src dst)
exchangeRateFromDecimal yst sf = \case
('-':_) -> Nothing
str -> exchangeRate =<< rationalFromDecimal yst sf str
rationalFromDecimal
:: Maybe Char
-> Char
-> String
-> Maybe Rational
rationalFromDecimal yst sf = \s ->
case ReadP.readP_to_S (rationalFromDecimalP yst sf) s of
[(x,"")] -> Just x
_ -> Nothing
rationalFromDecimalP
:: Maybe Char
-> Char
-> 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)