[Haskell-cafe] phantom types and record syntax

Tikhon Jelvis tikhon at jelv.is
Thu Jun 18 03:10:14 UTC 2015


Record updates have to be able to change the type of the value in order to
work with polymorphic fields. Here's a contrived example:

data Foo a = Foo { foo :: a }

reFoo x = x { foo = "blarg" }

By the same logic, the resulting type can change the phantom parameter
because the phantom parameter is not constrained at all. You could have the
same problem without record syntax:

unsafeId (Username first last) = Username first last

That looks fine—but completely bypasses the phantom type! (In fact, it
could even produce a Username Int or something.)

This is a fundamental limitation of phantom types. Since they're not
constrained at all, they're very easy to change. They're useful, but more
as a convention and a warning than as an ironclad guarantee.

In this specific case, I can think of two reasonable options. One is to
make the Username type abstract—don't export the constructor and ensure
that the only provided ways to make one are safe. Another is to make a
String newtype with a flag, and keep that abstract. Domain-specific types
like Username could use this newtype for their fields.

newtype Str a = Str String

data Username a = Username { first :: Str a, last :: Str a }

As long as you have access to a Str constructor, you can make "Safe"
instances however you like; however, if you keep the type abstract, it can
be safe outside the defining module.

There are some other design options, and it's something worth thinking
about. But what you've found is a fundamental quality (and limitation) of
phantom types and has to be kept in mind as you're working with them.


On Wed, Jun 17, 2015 at 7:45 PM, Dimitri DeFigueiredo <
defigueiredo at ucdavis.edu> wrote:

>  Hi All,
>
> My apologies if this is not the right forum, but am not satisfied with my
> current understanding.
>
> I am surprised that this program compiles in GHC:
>
> -----
> data UserSupplied  = UserSupplied -- i.e. unsafe
> data Safe          = Safe
>
> data Username a = Username { first :: String, last :: String}
>
> sanitize :: Username UserSupplied -> Username Safe
> sanitize name = name { first = "John" }
>
> main = putStrLn "Hi!"
> -----
>
> My trouble is that it seems the record syntax is *implicitly* converting
> from one type to the other. It seems I would have to remove the phantom
> type by adding a tag to avoid this:
>
> -----
> data Username a = Username { first :: String, last :: String, tag :: a }
>
> sanitize :: Username UserSupplied -> Username Safe
> sanitize name = name { first = "John" } -- FAIL as expected!! :-)
> -----
>
> But this makes me unwilling to use phantom types for security as I would
> be worried of unwittingly making the conversion.
>
> Could somebody sprinkle some insight into why this is "converted
> automatically" for phantom types?
> Is there another way around this?
>
>
> Thanks!
>
>
> Dimitri
>
>
>
> _______________________________________________
> Haskell-Cafe mailing list
> Haskell-Cafe at haskell.org
> http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20150617/5b1a54d3/attachment.html>


More information about the Haskell-Cafe mailing list