GHC/Coercible

From HaskellWiki
< GHC
Revision as of 09:16, 20 May 2014 by Nomeata (talk | contribs) (Created page with "This page contains additional information about <hask>Coercible</hask> and augments the [http://hackage.haskell.org/package/base/docs/Data-Coerce.html documentation] and the [...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

This page contains additional information about Coercible and augments the documentation and the ICFP 2014 paper. This is a feature that first appeared in GHC 7.8.1 and will likely evolve futher.

The problem

Given a newtype

newtype HTML = MkHTML String

we can convert between HTML and String with

toHTML :: String -> HTML
toHTML s = MkHTML s

fromHTML :: HTML -> String
fromHTML (MkHTML s) = s

and these conversions are free, i.e. they have no run-time cost.

But how do we get from [String] to [HTML]? We can write

toHTMLs :: [String] -> [HTML]
toHTMLs = map MkHTML

but the execution of map incurs a cost at run-time.

Using Coercible

The solution available since GHC-7.8.1 is to use coerce from the module Data.Coerce:

import Data.Coerce
toHTMLs :: [String] -> [HTML]
toHTMLs = coerce

It works like unsafeCoerce, i.e. has no run-time cost, but the type checker ensures that it really is safe to use it. If you use it illegally like in

wrong :: [Int -> [Bool]
wrong = coerce

you will get an error message:

    Could not coerce from ‘Int’ to ‘Bool’
      because ‘Int’
          and ‘Bool’
          are different types.
      arising from a use of ‘coerce’
    In the expression: coerce
    In an equation for ‘wrong’: wrong = coerce

The type of coerce is Coercible a b => a -> b, and the instances of the “type class” Coercible (which behaves almost like a regular type class) ensure that Coercible s t is only solvable if s and t have the same run-time representation.

Interesting things to note

Using newtypes internally and externally differently

You can unwrap a newtype using coerce only if the corresponding constructor is in scope. This allows you to do free conversions in your own library (where the constructor is in scope), while controlling what others can do using role annotation:

This newtype has a phantom parameter, but the role annotation allows users of the library to coerce NT s to NT t only if Coercible s t holds:

newtype NT a = MkNT ()
type role NT representational

Nevertheless, as long as the constructor MkNT is in scope, we can do coerce :: NT Bool -> NT Int, if we wish to do so.

(This does not yet work in GHC-7.8, as a bug in GHC was fixed only later.)