It might be nice to show the Ruby comparison piece using a typeclass (i.e. Haskell-vtable). It ends up showing a lot of extra boilerplate and spreads interfaces all around the program.
Also the real joy of GADTs come from what happens to type parameters when you pattern match on them.
type family Result a :: *
type instance Result PlainText = Int
type instance Result Encrypted = String
foo :: Message a -> Result a
foo (EncryptedMessage _) = "hi!"
foo (PlainTextMessage _) = 0
Obviously a foolish example, but it’s pretty amazing to think what power is exposed there.
Which is exactly why you’d use phantom types instead. Don’t invoke power you don’t much need.
This is the key. Power is wonderful, but is sometimes overkill for what you need. Put another way, just because you can do something with GADTs (or template metaprogramming, or reflection, or any other powerful language feature) doesn’t mean you should. Phantom types are a simple solution to a simple problem, and sometimes that’s all you need.
I don’t agree think Phantom Types improve upon the normal way of doing this:
class Message m where
messageLength :: m -> Int
send :: m -> IO ()
newtype PlainText = PlainText String
newtype Encrypted = Encrypted String
instance Message PlainText where
messageLength (PlainText s) = length s
instance Message Encrypted where
messageLength (Encrypted s) = length s
encrypt :: PlainText -> Encrypted
decrypt :: Encrypted -> PlainText
Unlike doing it with phantom types this way lets you change the underlying data structure for one of the Message types, or lets you add new ones which have a different implementation than String.
This allows for an implementation of send for plain text messages that isn’t caught by the type checker. Let’s also not get too caught up in the details of this particular example: phantom types are great if you have a data type (with identical representation) that shares structure, but you want to track a property statically. (By the way, the instances lack the implementation of send.)
This allows for an implementation of send for plain text messages that isn’t caught by the type checker
If you only want to be able to send encrypted messages then leave send :: m -> IO () out of the typeclass and implement send :: Encrypted -> IO (). Near the end of the post he was discussing send :: Message -> IO () and I was demonstrating how you’d do that.
Let’s also not get too caught up in the details of this particular example
He is writing a book called “haskell by example” so I think that it’s on point to focus on the particular example given. I agree with your general outline of when phantom types are useful.
By the way, the instances lack the implementation of send
yes.. the implementation of send is not relevant and not shown in the blog post either.