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)