# Using phantom types to represent amounts and exchange rates of currencies

I posted the following snippet to StackOverflow, and someone said there was much to be improved.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}

class (Show a) => Currency a where unphantom :: a

data USD = USD deriving Show
data EUR = EUR deriving Show

instance Currency USD where unphantom = USD
instance Currency EUR where unphantom = EUR

data Amount a where
Amount :: Currency a => Float -> Amount a
instance Show (Amount a) where
show (Amount x) = show x ++ show (unphantom :: a)

data Rate a b where
Rate :: (Currency a, Currency b) => Float -> Rate a b
-- ...


Having to define an unphantom function for each currency is a bit annoying but I don't really see a way around it. Also, note that my use of Float to represent monetary values is just a placeholder.

How can I improve this code?

You could rely on the fact the types describing your currencies are Enumerable and you can therefore always generate the appropriate token based on the type of an expression. You would write something like this:

{-# LANGUAGE ScopedTypeVariables #-}

module Currency where

data USD = USD deriving (Show, Enum)
data EUR = EUR deriving (Show, Enum)

currencyRep :: Enum a => a
currencyRep = toEnum 0

newtype Amount a = Amount { value :: Float }

instance (Enum a, Show a) => Show (Amount a) where
show v = show (value v) ++ " " ++ show (currencyRep :: a)

newtype Rate a b = Rate { rate :: Float }

convert :: Rate a b -> Amount a -> Amount b
convert k v = Amount (rate k * value v)

-- example:
main :: IO ()
main = putStrLn \$ show (Amount 3 :: Amount EUR)


Now you can really talk about phantom types: Amount does not carry around a representation of the currency being used and both Amount and Rate are newtypes meaning that they will be optimized away.

Question: Is there a typeclass of singletons out there? It'd be cleaner than using Enum (though relying on deriving is pretty useful) if we had something like this:

class Singleton a where
unique :: a

• It's not standard, but the package 'Default' does what you want. It's not deriveable so I think you're better off with Enum... – yatima2975 Jul 31 '14 at 11:17