Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to model a currencies, money, and banks that exchange money between currencies?

Tags:

types

haskell

Hey so I was reading this post on type-driven development in Java. I had trouble grokking the Java types, so I tried writing it in Haskell. However, I have two problems:

  1. I don't know how to implement the difference between a currency and an actual bit of money. At first I thought the currency was just the type of the money (and I think that makes sense) like this data Dollar = Dollar Double, where a value like Dollar 4.0 is money, and Dollar the type is the currency. And I think Dollar :: Double -> Dollar would be something not exported.
  2. That leads to the problem that I can't model a bank that exchanges money. I was thinking something likeexchange :: (Money a, Money b) =>[ExchangeRate] -> a -> b. Then a bank is just an object that contains a collection of ExchangeRates, but I don't know what type ExchangeRate is.

The code I have so far is:

class Money m where
    money :: (Money m) => Double -> m
    amount :: (Money m) => m -> Double
    add :: (Money m) => m -> m -> m
    add a b = money $ amount a + amount b

class (Money a, Money b) => ExchangeablePair a b where

newtype Dollar = Dollar Double
                 deriving (Show, Eq)

instance Money Dollar where
    money = Dollar
    amount (Dollar a) = a

newtype Franc = Franc Double
                 deriving (Show, Eq)

instance Money Franc where
    money = Franc
    amount (Franc a) = a

instance ExchangeablePair Dollar Franc where

EDIT: I still want the safety of something like this: buyAmericanBigMac :: Dollar -> (BigMac, Dollar).

like image 792
Ramith Jayatilleka Avatar asked Dec 10 '14 19:12

Ramith Jayatilleka


People also ask

How do banks exchange currencies?

Once a bank or firm quotes the exchange rate, the customer chooses to accept the rate or not. If they accept, the transaction goes through. On top of the exchange rate conversion, certain transaction fees might also be applied to send or receive an international money transfer.

What is the formula for converting currencies?

The formula is: Starting Amount (Original Currency) / Ending Amount (New Currency) = Exchange Rate. For example, if you exchange 100 U.S. Dollars for 80 Euros, the exchange rate would be 1.25. But if you exchange 80 Euros for 100 U.S. Dollars, the exchange rate would be 0.8. Calculate the foreign currency amount.


1 Answers

First note that to be safe, exchange should have type

exchange :: (Money a, Money b) => [ExchangeRate] -> a -> Maybe b

because if you don't have a or b in your list of rates you can't return anything.

For ExchangeRate we could use:

newtype ExchangeRate = Rate { unrate :: (TypeRep, Double) }
  deriving Show

The TypeRep is a unique "fingerprint" for a type. You can get a TypeRep by calling typeOf on something with a Typeable instance. Using this class we can write a type safe lookup for exchange rates:

findRate :: Typeable a => [ExchangeRate] -> a -> Maybe Double
findRate rates a = lookup (typeOf a) (map unrate rates)

Then we can implement your exchange function:

exchange :: forall a b. (Money a, Money b) => [ExchangeRate] -> a -> Maybe b
exchange rates a = do
  aRate <- findRate rates a
  bRate <- findRate rates (undefined :: b)

  return $ money (bRate * (amount a / aRate))

Here we use the ScopedTypeVariables extension so we can write undefined :: b (note we need to write forall a b. as well for this to work)

Here's a minimal working example. Instead of [ExchangeRate] I've used a HashMap (it's faster and stops users from combining exchanges rates that don't belong together).

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveDataTypeable #-}

module Exchange
  ( Dollar
  , Franc
  , exchange

  , sampleRates
  , sampleDollars
  ) where

import Data.HashMap.Strict as HM
import Data.Typeable

class Typeable m => Money m where
  money  :: Money m => Double -> m
  amount :: Money m => m -> Double
  add    :: Money m => m -> m -> m
  add a b = money $ amount a + amount b

newtype Dollar = Dollar Double
  deriving (Show, Eq, Typeable)

instance Money Dollar where
  money = Dollar
  amount (Dollar a) = a

newtype Franc = Franc Double
  deriving (Show, Eq, Typeable)

instance Money Franc where
  money            = Franc
  amount (Franc a) = a

newtype ExchangeRates = Exchange (HashMap TypeRep Double)
  deriving Show

findRate :: Typeable a => ExchangeRates -> a -> Maybe Double
findRate (Exchange m) a = HM.lookup (typeOf a) m

exchange :: forall a b. (Money a, Money b) => ExchangeRates -> a -> Maybe b
exchange rates a = do
  aRate <- findRate rates a
  bRate <- findRate rates (undefined :: b)

  return $ money (bRate * (amount a / aRate))

sampleRates :: ExchangeRates
sampleRates = Exchange $ HM.fromList
  [ (typeOf (Dollar 0), 1)
  , (typeOf (Franc 0) , 1.2)
  ]

sampleDollars :: Dollar
sampleDollars = Dollar 5

Then you can write

> exchange sampleRates sampleDollars :: Maybe Franc
Just (Franc 6.0)

As other other people have mentioned, Double isn't really suitable because you can get floating point errors. If you're doing anything with real money I'd recommend using scientific.

like image 146
cchalmers Avatar answered Oct 20 '22 00:10

cchalmers