5
\$\begingroup\$

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?

\$\endgroup\$
4
\$\begingroup\$

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
\$\endgroup\$
  • 1
    \$\begingroup\$ 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... \$\endgroup\$ – yatima2975 Jul 31 '14 at 11:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.