Based on apfelmus’ reply on the libraries mailing list it seems I got phantom types a bit wrong. So yesterday I set out to try to understand a bit better.

I got my inspiration from two blog posts in particular, Neil Mitchell’s and Lennart Augustsson’s. I am not sure I’m adding very much beyond the latter of the two but just in case it’ll help someone I’m documenting this anyway.

This is a rather contrived example but it’s short and simple. I have a type that can have two types of values:

```
type MyType = Either Int String
```

`Int`

s can be added and `String`

s can be concatenated:

```
add :: MyType -> MyType -> MyType
add (Left x) (Left y) = Left $ x + y
cat :: MyType -> MyType -> MyType
cat (Right a) (Right b) = Right $ a ++ b
```

The problem with this of course is that nothing prevents me from trying to pass a `Left`

and a `Right`

to either of the functions. Both would result in errors as it stands. One option would of course be to explicitly deal with the error cases in code, but wouldn’t it be *much* nicer if it would somehow be possible to catch those error cases already at compile time?

This is exactly what phantom types allow, and it does it within “regular” Haskell^{i}.

First I need a new type:

```
newtype MyPh a = MyPh MyType deriving (Show)
```

Then I rename the two functions above, `add`

and `cat`

, as `add'`

and `cat'`

and introduce two “wrapper functions”:

```
add :: MyPh Int -> MyPh Int -> MyPh Int
add (MyPh x) (MyPh y) = MyPh $ add' x y
cat :: MyPh String -> MyPh String -> MyPh String
cat (MyPh a) (MyPh b) = MyPh $ cat' a b
```

The type signatures on these functions are the important part. Without them Haskell would infer the type `MyPh a -> MyPh b -> MyPh c`

for both functions and I gain nothing by that. What is left to do is to add constructors:

```
phInt :: Int -> MyPh Int
phInt = MyPh . Left
phString :: String -> MyPh String
phString = MyPh . Right
```

Again the type signatures are crucial.

Now, to make sure that the user of this API doesn’t mess with the type safety that I’ve introduced I must take care to export the phantom type but not its constructor (`MyPh`

), the two manually written constructors (`phInt`

and `phString`

), and the safe functions for adding and concatenating (`add`

and `cat`

).

[*Edited 17-10-2007*] Corrected the inferred type after Derek’s comment.

- As Lennart A points out in his post GADT’s offer a very elegant way of achieving the same results. However, GADT’s aren’t part of Haskell 98.[back]

You would gain something if that was the type signature that Haskell inferred for add/cat*, but that isn’t the type Haskell would infer. It would infer MyPh a -> MyPh b -> MyPh c and -that- would give you nothing.

* In fact, you could combine them together into one function with that type (the one you listed) and it would be just as safe as having each of them (at least in the way you discussed in the post.)

I did mention that the inferred type isn’t of much use. Even though I got the inferred type slightly wrong (it’s been corrected by now though).

AFAIU the Haskell type system could

neverinfer the types`MyPh Int`

and`MyPh String`

, and whether they are inferred or not isn’t really the point withphantom types. The point is that the implementation doesn’t need to handle some error cases that could arise otherwise, and the user of the API gets help from the compiler to make sure the types match up.I don’t understand what you mean by “you could combine them together into one function with that type”. Please explain further.

add’ (Left x) (Left y) = Left (x+y) add’ (Right x) (Right y) = Right (x++y)

add :: MyPh a -> MyPh a -> MyPh a add (MyPh x) (MyPh y) = MyPh (add’ x y)

The uncovered cases in add’ can never come up if all you provide is add and phString, phInt.

Derek, you are absolutely right. I don’t want to get into an argument here, but

you still have to explicitly set the type of. If you don’t it ends up with the typeadd`MyPh a -> MyPh b -> MyPh c`

anyway. So, in the strictest sense, I don’t see your code allowing any type inference to take place either.