Search code examples
haskellnewtypephantom-types

How do phantom types work with newtype?


My understanding of newtypes is that they are compiled out by GHC. However, this can't be the whole story because phantom types can hold information.

From here:

you can wrap [a type] in a newtype and it'll be considered distinct to the type-checker, but identical at runtime. You can then use all sorts of deep trickery like phantom or recursive types without worrying about GHC shuffling buckets of bytes for no reason.

For example, imagine a newtype representing arithmetic modulo q:

newtype Zq q = Zq Int

class Modulus q where
    getModulus :: q -> Int

addZq :: (Modulus q) => Zq q -> Zq q -> Zq q
addZq (Zq a) (Zq b) = Zq $ (a+b) `mod` (getModulus (undefined :: q))

addZq can't be compiled down to

addZq :: Int -> Int -> Int

so in what sense is the newtype compiled out, and where does the phantom type information get stored?


Solution

  • The thing to keep in mind is that you don't "compile down" to Haskell; you compile down to some other, more explicit language -- in GHC's case, the next well-known step down is core. And although you can't compile addZq down to something of type Int -> Int -> Int in Core, you can compile it down to something whose type you might write as Modulus q => Int -> Int -> Int. In this more explicit language, => has a different meaning than in Haskell; in this language, c => t is the type of a function which takes evidence (in this case, a class dictionary) for the claim c and produces something of type t. So Modulus q => Int -> Int -> Int is roughly the same as (q -> Int) -> Int -> Int -> Int, and addZq certainly can be given that type, even in Haskell.