简体   繁体   中英

Creating (getting) a value instance from a phantom type

I'm using GADTs to create a basic dimension (as in physical dimensions) system for currencies. The dimensions (eg 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 eg "10.3USD" or "0EUR" and a rate as eg "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 (ie 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM