0
votes

I declared a a phantom type like this with Haskell.

newtype Length (a::UnitLength) b = Length b deriving (Eq,Show)
data UnitLength = Meter
                | KiloMeter
                | Miles
                deriving (Eq,Show)

Now, I would like to write some functions to use this type. But I didn't happened to see and use the hidden type.

Is it possible to retrieve the hidden type a of the phantom type Length to perform test, pattern matching, .... ?

1
The simplest way to do any computation with types is with type classes. What function in particular would you want to write? - user2407038

1 Answers

5
votes

If you want a runtime representation of the phantom type you have used, you have to use what we call a singleton. It has precisely one constructor for each ones of the constructors in UnitLength and their types say precisely which constructor we are considering:

data SUnitLength (a :: UnitLength) where
  SMeter     :: SUnitLength Meter
  SKiloMeter :: SUnitLength KiloMeter
  SMiles     :: SUnitLength Miles

Now that you have this you can for instance write a display function picking the right unit abbreviation depending on the phantom parameter:

display :: Show b => SUnitLength a -> Length a b -> String
display sa l = show (payload l) ++
  case sa of
    SKiloMeter -> "km"
    _          -> "m"

Now, that does not really match your demand: the parameter a is available in the type Length a b but we somehow still have to manufacture the witness by hand. That's annoying. One way to avoid this issue is to define a type class doing that work for us. CUnitLength a tells us that provided a value of type Length a b, we can get a witness SUnitLength a of the shape a has.

class CUnitLength (a :: UnitLength) where
  getUnit :: Length a b -> SUnitLength a

It is easy for us to write instances of CUnitLength for the various UnitLength constructors: getUnit can even ignore its argument!

instance CUnitLength Meter where
  getUnit _ = SMeter

instance CUnitLength KiloMeter where
  getUnit _ = SKiloMeter

instance CUnitLength Miles where
  getUnit _ = SMiles

So why bother with getUnit's argument? Well if we remove it, getUnit needs to somehow magically guess which a it is suppose to describe. Sometimes it's possible to infer that ̀a based on the expected type at the call site but sometimes it's not. Having the Length a b argument guarantees that all calls will be unambiguous. We can always recover a simpler getUnit' anyway:

getUnit' :: CUnitLength a => SUnitLength a
getUnit' = getUnit (undefined :: Length a ())

Which leads us to the last definition display' which has the same role as display but does not require the extra argument:

display' :: (CUnitLength a, Show b) => Length a b -> String
display' = display getUnit'

I have put everything (including the LANGUAGE extensions, and the definition of payload to extract a b from Length a b) in a self-contained gist in case you want to play with the code.