I'm using GADTs to create a basic dimension (as in physical dimensions) system for currencies. The dimensions (e.g. USD, USD/EUR, EUR/USD) are represented as phantom types. I would like to be able to print an amount of a currency in the style of e.g. "10.3USD" or "0EUR" and a rate as e.g. "10.3USD/EUR" using Show. I'm not quite sure how to explain my problem, so I'll go for an example of how I tried to solve it:
{-# LANGUAGE GADTs #-}
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
-- ...
With this code, I get the error
$ ghc example.hs
[1 of 1] Compiling Main ( example.hs, example.o )
example.hs:14:37:
Could not deduce (Currency a1) arising from a use of `unphantom'
from the context (Currency a)
bound by a pattern with constructor
Amount :: forall a. Currency a => Float -> Amount a,
in an equation for `show'
at example.hs:14:9-16
Possible fix:
add (Currency a1) to the context of
an expression type signature: a1
or the data constructor `Amount'
or the instance declaration
In the first argument of `show', namely `(unphantom :: a)'
In the second argument of `(++)', namely `show (unphantom :: a)'
In the expression: show x ++ show (unphantom :: a)
I must say I don't understand why the compiler in this case is talking about an a1
type when I specified a
.
Of course, I want to avoid representing the dimensions outside the haskell type system since this adds extra boilerplate code for me and is as far as I can tell theoretically unnecessary (i.e. the compiler should have enough information to deduce how to show an Amount or a Rate at compile time) (, and adds a little bit of overhead at runtime).
Use ScopedTypeVariables
and your code compiles as is.
In particular, without ScopedTypeVariables
when you write
instance Show (Amount a) where
show (Amount x) = show x ++ show (unphantom :: a)
the a
in unphantom :: a
is fresh and not made to unify with the a
in instance Show (Amount a) where
. Turning on ScopedTypeVariables
forces it to unify.