diff options
Diffstat (limited to 'docs/users_guide/exts/roles.rst')
-rw-r--r-- | docs/users_guide/exts/roles.rst | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/docs/users_guide/exts/roles.rst b/docs/users_guide/exts/roles.rst new file mode 100644 index 0000000000..a92a78787d --- /dev/null +++ b/docs/users_guide/exts/roles.rst @@ -0,0 +1,228 @@ +.. _roles: + +Roles +===== + +.. index:: + single: roles + +Using :extension:`GeneralizedNewtypeDeriving` +(:ref:`newtype-deriving`), a programmer can take existing +instances of classes and "lift" these into instances of that class for a +newtype. However, this is not always safe. For example, consider the +following: + +:: + + newtype Age = MkAge { unAge :: Int } + + type family Inspect x + type instance Inspect Age = Int + type instance Inspect Int = Bool + + class BadIdea a where + bad :: a -> Inspect a + + instance BadIdea Int where + bad = (> 0) + + deriving instance BadIdea Age -- not allowed! + +If the derived instance were allowed, what would the type of its method +``bad`` be? It would seem to be ``Age -> Inspect Age``, which is +equivalent to ``Age -> Int``, according to the type family ``Inspect``. +Yet, if we simply adapt the implementation from the instance for +``Int``, the implementation for ``bad`` produces a ``Bool``, and we have +trouble. + +The way to identify such situations is to have *roles* assigned to type +variables of datatypes, classes, and type synonyms. + +Roles as implemented in GHC are a from a simplified version of the work +described in `Generative type abstraction and type-level +computation <http://www.seas.upenn.edu/~sweirich/papers/popl163af-weirich.pdf>`__, +published at POPL 2011. + +.. _nominal-representational-and-phantom: + +Nominal, Representational, and Phantom +-------------------------------------- + +.. index:: + single: representational; role + single: nominal; role + single: phantom; role + +The goal of the roles system is to track when two types have the same +underlying representation. In the example above, ``Age`` and ``Int`` +have the same representation. But, the corresponding instances of +``BadIdea`` would *not* have the same representation, because the types +of the implementations of ``bad`` would be different. + +Suppose we have two uses of a type constructor, each applied to the same +parameters except for one difference. (For example, ``T Age Bool c`` and +``T Int Bool c`` for some type ``T``.) The role of a type parameter says +what we need to know about the two differing type arguments in order to +know that the two outer types have the same representation (in the +example, what must be true about ``Age`` and ``Int`` in order to show +that ``T Age Bool c`` has the same representation as ``T Int Bool c``). + +GHC supports three different roles for type parameters: nominal, +representational, and phantom. If a type parameter has a nominal role, +then the two types that differ must not actually differ at all: they +must be identical (after type family reduction). If a type parameter has +a representational role, then the two types must have the same +representation. (If ``T``\'s first parameter's role is representational, +then ``T Age Bool c`` and ``T Int Bool c`` would have the same +representation, because ``Age`` and ``Int`` have the same +representation.) If a type parameter has a phantom role, then we need no +further information. + +Here are some examples: :: + + data Simple a = MkSimple a -- a has role representational + + type family F + type instance F Int = Bool + type instance F Age = Char + + data Complex a = MkComplex (F a) -- a has role nominal + + data Phant a = MkPhant Bool -- a has role phantom + +The type ``Simple`` has its parameter at role representational, which is +generally the most common case. ``Simple Age`` would have the same +representation as ``Simple Int``. The type ``Complex``, on the other +hand, has its parameter at role nominal, because ``Complex Age`` and +``Complex Int`` are *not* the same. Lastly, ``Phant Age`` and +``Phant Bool`` have the same representation, even though ``Age`` and +``Bool`` are unrelated. + +.. _role-inference: + +Role inference +-------------- + +What role should a given type parameter should have? GHC performs role +inference to determine the correct role for every parameter. It starts +with a few base facts: ``(->)`` has two representational parameters; +``(~)`` has two nominal parameters; all type families' parameters are +nominal; and all GADT-like parameters are nominal. Then, these facts are +propagated to all places where these types are used. The default role +for datatypes and synonyms is phantom; the default role for classes is +nominal. Thus, for datatypes and synonyms, any parameters unused in the +right-hand side (or used only in other types in phantom positions) will +be phantom. Whenever a parameter is used in a representational position +(that is, used as a type argument to a constructor whose corresponding +variable is at role representational), we raise its role from phantom to +representational. Similarly, when a parameter is used in a nominal +position, its role is upgraded to nominal. We never downgrade a role +from nominal to phantom or representational, or from representational to +phantom. In this way, we infer the most-general role for each parameter. + +Classes have their roles default to nominal to promote coherence of +class instances. If a ``C Int`` were stored in a datatype, it would be +quite bad if that were somehow changed into a ``C Age`` somewhere, +especially if another ``C Age`` had been declared! + +There is one particularly tricky case that should be explained: :: + + data Tricky a b = MkTricky (a b) + +What should ``Tricky``'s roles be? At first blush, it would seem that +both ``a`` and ``b`` should be at role representational, since both are +used in the right-hand side and neither is involved in a type family. +However, this would be wrong, as the following example shows: :: + + data Nom a = MkNom (F a) -- type family F from example above + +Is ``Tricky Nom Age`` representationally equal to ``Tricky Nom Int``? +No! The former stores a ``Char`` and the latter stores a ``Bool``. The +solution to this is to require all parameters to type variables to have +role nominal. Thus, GHC would infer role representational for ``a`` but +role nominal for ``b``. + +.. _role-annotations: + +Role annotations +---------------- + +.. extension:: RoleAnnotations + :shortdesc: Enable role annotations. + + :since: 7.8.1 + + Allow role annotation syntax. + +Sometimes the programmer wants to constrain the inference process. For +example, the base library contains the following definition: :: + + data Ptr a = Ptr Addr# + +The idea is that ``a`` should really be a representational parameter, +but role inference assigns it to phantom. This makes some level of +sense: a pointer to an ``Int`` really is representationally the same as +a pointer to a ``Bool``. But, that's not at all how we want to use +``Ptr``\ s! So, we want to be able to say :: + + type role Ptr representational + data Ptr a = Ptr Addr# + +The ``type role`` (enabled with :extension:`RoleAnnotations`) declaration +forces the parameter ``a`` to be at role representational, not role +phantom. GHC then checks the user-supplied roles to make sure they don't +break any promises. It would be bad, for example, if the user could make +``BadIdea``\'s role be representational. + +As another example, we can consider a type ``Set a`` that represents a +set of data, ordered according to ``a``\'s ``Ord`` instance. While it +would generally be type-safe to consider ``a`` to be at role +representational, it is possible that a ``newtype`` and its base type +have *different* orderings encoded in their respective ``Ord`` +instances. This would lead to misbehavior at runtime. So, the author of +the ``Set`` datatype would like its parameter to be at role nominal. +This would be done with a declaration :: + + type role Set nominal + +Role annotations can also be used should a programmer wish to write a +class with a representational (or phantom) role. However, as a class +with non-nominal roles can quickly lead to class instance incoherence, +it is necessary to also specify :extension:`IncoherentInstances` to allow +non-nominal roles for classes. + +The other place where role annotations may be necessary are in +``hs-boot`` files (:ref:`mutual-recursion`), where the right-hand sides +of definitions can be omitted. As usual, the types/classes declared in +an ``hs-boot`` file must match up with the definitions in the ``hs`` +file, including down to the roles. The default role for datatypes is +representational in ``hs-boot`` files, corresponding to the common use +case. + +Role annotations are allowed on data, newtype, and class declarations. A +role annotation declaration starts with ``type role`` and is followed by +one role listing for each parameter of the type. (This parameter count +includes parameters implicitly specified by a kind signature in a +GADT-style data or newtype declaration.) Each role listing is a role +(``nominal``, ``representational``, or ``phantom``) or a ``_``. Using a +``_`` says that GHC should infer that role. The role annotation may go +anywhere in the same module as the datatype or class definition (much +like a value-level type signature). Here are some examples: :: + + type role T1 _ phantom + data T1 a b = MkT1 a -- b is not used; annotation is fine but unnecessary + + type role T2 _ phantom + data T2 a b = MkT2 b -- ERROR: b is used and cannot be phantom + + type role T3 _ nominal + data T3 a b = MkT3 a -- OK: nominal is higher than necessary, but safe + + type role T4 nominal + data T4 a = MkT4 (a Int) -- OK, but nominal is higher than necessary + + type role C representational _ -- OK, with -XIncoherentInstances + class C a b where ... -- OK, b will get a nominal role + + type role X nominal + type X a = ... -- ERROR: role annotations not allowed for type synonyms |