packages feed

safe-money 0.4 → 0.4.1

raw patch · 5 files changed

+74/−8 lines, 5 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

changelog.md view
@@ -1,3 +1,8 @@+# Version 0.4.1++* `ExchangeRate` is now a `Category`.++ # Version 0.4  * BREAKING CHANGE. COMPILER WON'T COMPLAIN. HUMAN INTERVENTION POTENTIALLY
safe-money.cabal view
@@ -1,5 +1,5 @@ name: safe-money-version: 0.4+version: 0.4.1 license: BSD3 license-file: LICENSE copyright: Copyright (c) Renzo Carbonara 2016-2017
src/Money.hs view
@@ -14,6 +14,10 @@ -- a new 'I.Scale' instance, and /voilà/. If you want to add a new currency to the -- out-of-the-box offer, please request so in -- https://github.com/k0001/safe-money/issues and the authors will see to it.+--+-- 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 module Money  ( -- * Dense monetary values    I.Dense
src/Money/Internal.hs view
@@ -71,6 +71,7 @@  ) where  import Control.Applicative ((<|>), empty)+import Control.Category (Category((.), id)) import Control.Monad ((<=<), guard, when) import Data.Constraint (Dict(Dict)) import Data.Monoid ((<>))@@ -82,7 +83,7 @@ import GHC.TypeLits   (Symbol, SomeSymbol(..), Nat, SomeNat(..), CmpNat, KnownSymbol, KnownNat,    natVal, someNatVal, symbolVal, someSymbolVal)-import Prelude hiding (round, ceiling, floor, truncate)+import Prelude hiding ((.), round, ceiling, floor, truncate) import qualified Prelude import qualified Text.ParserCombinators.ReadPrec as ReadPrec import qualified Text.ParserCombinators.ReadP as ReadP@@ -353,7 +354,7 @@ -- then the following holds: -- -- @--- 'ceiling' x == (y, 'Just' z)+-- 'ceiling' x == (y, z) -- @ -- -- @@@ -361,7 +362,7 @@ -- @ -- -- @--- z < 'zero'+-- z < 0 -- @ -- -- Proof that 'ceiling' doesn't lose money:@@ -391,7 +392,7 @@ -- then the following holds: -- -- @--- 'floor' x == (y, 'Just' z)+-- 'floor' x == (y, z) -- @ -- -- @@@ -399,7 +400,7 @@ -- @ -- -- @--- z > 'zero'+-- z > 0 -- @ -- -- Proof that 'floor' doesn't lose money:@@ -582,6 +583,42 @@ 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@@ -624,7 +661,8 @@  -- | Flip the direction of an 'ExchangeRate'. ----- Identity law:+-- This function retuns the multiplicative inverse of the given 'ExchangeRate',+-- leading to the following identity law: -- -- @ -- 'flipExchangeRate' . 'flipExchangeRate'   ==  'id'
test/Main.hs view
@@ -8,11 +8,13 @@  module Main where +import Control.Category (Category((.), id)) import qualified Data.ByteString.Lazy as BSL import Data.Maybe (catMaybes, isJust, isNothing) import Data.Proxy (Proxy(Proxy)) import Data.Ratio (numerator, denominator) import GHC.TypeLits (Nat, Symbol, KnownSymbol, symbolVal)+import Prelude hiding ((.), id) import qualified Test.Tasty as Tasty import qualified Test.Tasty.Runners as Tasty import Test.Tasty.QuickCheck ((===), (==>), (.&&.))@@ -446,7 +448,24 @@ testExchangeRate ps pd =   Tasty.testGroup ("ExchangeRate " ++ show (symbolVal ps) ++ " "                                    ++ show (symbolVal pd))-  [ QC.testProperty "read . show == id" $+  [ QC.testProperty "Category: left identity" $+      QC.forAll QC.arbitrary $ \(xr :: Money.ExchangeRate src dst) ->+         xr === id . xr+  , QC.testProperty "Category: right identity" $+      QC.forAll QC.arbitrary $ \(xr :: Money.ExchangeRate src dst) ->+         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))+  , 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))++  , QC.testProperty "read . show == id" $       QC.forAll QC.arbitrary $ \(xr :: Money.ExchangeRate src dst) ->          xr === read (show xr)   , QC.testProperty "read . show . Just == Just" $