summaryrefslogtreecommitdiff
path: root/docs/users_guide/glasgow_exts.xml
diff options
context:
space:
mode:
Diffstat (limited to 'docs/users_guide/glasgow_exts.xml')
-rw-r--r--docs/users_guide/glasgow_exts.xml307
1 files changed, 307 insertions, 0 deletions
diff --git a/docs/users_guide/glasgow_exts.xml b/docs/users_guide/glasgow_exts.xml
index dd98f5ab66..00aaf92c4e 100644
--- a/docs/users_guide/glasgow_exts.xml
+++ b/docs/users_guide/glasgow_exts.xml
@@ -7176,6 +7176,313 @@ instance OkClsish () a => OkCls a where
</sect1>
+<sect1 id="overloaded-record-fields">
+<title>Overloaded record fields</title>
+
+<para>
+A serious limitation of the Haskell record system is the inability to
+overload field names in record types: for example, if the data types
+</para>
+
+<programlisting>
+data Person = Person { personId :: Int, name :: String }
+data Address = Address { personId :: Int, address :: String }
+</programlisting>
+
+<para>
+are declared in the same module, there is no way to determine which
+type an occurrence of the <literal>personId</literal> record selector
+refers to. A common workaround is to use a unique prefix for each
+record type, but this leads to less clear code and obfuscates
+relationships between fields of different records. Qualified names
+can be used to distinguish record selectors from different modules,
+but using one module per record is often impractical.
+</para>
+
+<para>
+Instead, the <option>-XOverloadedRecordFields</option> extension
+allows record field names to be overloaded and makes record
+projections polymorphic, so that the ambiguous identifier
+<literal>personId</literal> is resolved using the type of its
+argument. The extension introduces a new form of constraint
+<literal>r { x :: t }</literal>, meaning that type <literal>r</literal>
+has a field <literal>x</literal> of type <literal>t</literal>. (In
+fact, the constraint <literal>r { x :: t }</literal> is syntactic
+sugar for <literal>Has r "x" t</literal>, where the
+<literal>Has</literal> typeclass is defined in <ulink
+url="&libraryBaseLocation;/GHC-Records.html"><literal>GHC.Records</literal></ulink>, as discussed below.)
+A constraint <literal>R { x :: t }</literal> will be solved if
+<literal>R</literal> is a datatype that has a field
+<literal>x</literal> of monomorphic type <literal>t</literal> in
+scope. For example, the following declarations are accepted:
+</para>
+
+<programlisting>
+getPersonId :: r { personId :: Int } => r -> Int
+getPersonId v = personId v
+
+e = Person { personId = 0, name = "Me" }
+
+my_id = getPersonId e
+</programlisting>
+
+<para>
+An error is generated if <literal>R</literal> has no field called
+<literal>x</literal>, it has the wrong type, the type is existential
+or higher rank, or the field is not in scope. The restriction on
+types means that fields with higher-rank, universally quantified or
+existentially quantified types cannot be used with
+<option>-XOverloadedRecordFields</option>. More precisely, such
+fields will be in scope normally, but a constraint like
+<literal>R { x :: t }</literal> will not be solved if
+<literal>x</literal> has a quantified type. You can manually declare
+an appropriate selector function instead. The following declarations
+are rejected:
+</para>
+
+<programlisting>
+bad1 = personId True -- No instance for Bool { personId :: t }
+ -- since Bool does not have a personId field
+
+bad2 = personId e :: Bool -- Type Int of personId e is not Bool
+
+data HR = MkHR { unHR :: forall a . a -> a }
+bad3 = unHR (MkHR id) -- No instance for HR { unHR :: t }
+ -- since the field is higher-rank
+
+module M where
+ data U = MkU { foo :: Int }
+ data V = MkV { foo :: Int }
+
+module N where
+ import M ( U(MkU), V(foo) )
+ bad4 = foo (MkU 42) -- No instance for U { foo :: t }
+ -- since the field is not in scope
+</programlisting>
+
+
+<para>
+Note that a record field name must belong to at least one datatype for
+it to be used polymorphically in an expression. If
+<literal>g</literal> is not in scope, then the following declaration
+will be rejected:
+</para>
+
+<programlisting>
+f :: r { g :: Int } => r -> Int
+f x = g x + 1
+
+-- data T = MkT { g :: Char }
+</programlisting>
+
+<para>
+On the other hand, if the datatype declaration <literal>T</literal> is
+uncommented, then the program will be accepted, even though the type
+of the field does not match. That is, only a field of the correct
+name need be in scope; it need not have the same type.
+</para>
+
+<para>
+The syntax for record field constraints extends to conjunctions: for
+example, <literal>r { personId :: Int, age :: Int }</literal> is a
+valid constraint. Note also that the record and field types might be
+arbitrary types, not just variables or constructors. For example,
+<literal>(T (Maybe v)) { x :: [Maybe v] }</literal> is valid. In
+order to support these constraints, the
+<option>-XOverloadedRecordFields</option> extension implies
+<option>-XConstraintKinds</option> and
+<option>-XFlexibleContexts</option>.
+</para>
+
+<para>
+Furthermore, the <option>-XOverloadedRecordFields</option> extension
+implies <option>-XDisambiguateRecordFields</option> (<xref
+linkend="disambiguate-fields"/>). Thus record construction and
+pattern-matching always refer unambiguously to a single record type.
+Record updates (such as <literal>e { x = t }</literal>) also must be
+unambiguous. If the fields being updated are not unique to a single
+record type, then either the type must be determined by the context
+(e.g. from a type signature on the entire expression) or a type
+signature given on the record value. For example:
+</para>
+
+<programlisting>
+w = e { personId = 42 } -- ambiguous
+x = e { personId = 42, name = "Ma" } -- unambiguous as only Person has both fields
+y = (e :: Person) { personId = 42 } -- unambiguous due to type signature
+
+z :: Person
+z = e { personId = 42 } -- unambiguous due to type supplied by context
+</programlisting>
+
+<para>
+The <option>-XOverloadedRecordFields</option> extension permits
+overloading for the current module, regardless of whether the module
+that originally declared the datatype had the extension enabled.
+Conversely, if a module with the extension enabled defines a datatype,
+client modules without the extension will still interpret the fields
+as selector functions in the usual way.
+</para>
+
+<sect2 id="overloaded-record-fields-implementation">
+<title>Implementation details</title>
+
+<para>
+When the extension is enabled, a field <literal>foo</literal> has the
+following type:
+</para>
+
+<programlisting>
+foo :: (r { foo :: t }, Accessor p "foo") => p r t
+</programlisting>
+
+<para>
+It is expanded by the typechecker to an application of the
+<literal>field</literal> function, defined along with
+the <literal>Accessor</literal> class in
+<ulink url="&libraryBaseLocation;/GHC-Records.html"><literal>GHC.Records</literal></ulink>.
+This class has an instance for <literal>(->)</literal>, which will be
+selected whenever a field is used as a function. Thus
+</para>
+
+<programlisting>
+(\ x -> foo x) :: r { foo :: t } => r -> t
+</programlisting>
+
+<para>
+On the other hand, the extra polymorphism allows libraries to make
+their own use of fields. By providing an instance for
+<literal>Accessor</literal>, a library can turn an overloaded field
+into another datatype. This allows the library to expose overloaded
+record update.
+</para>
+
+<para>
+In all, there are two classes and two type families for which
+constraints are solved automatically. The classes are:
+<itemizedlist>
+<listitem><para>
+<literal>Has r f t</literal>, meaning that <literal>r</literal> has a
+field <literal>f</literal> of type <literal>t</literal>, used for
+desugaring <literal>r { x :: t }</literal>; and
+</para></listitem>
+<listitem><para>
+<literal>Upd r f u</literal>, meaning that <literal>r</literal> has a
+field <literal>f</literal> that can be assigned type <literal>u</literal>.
+</para></listitem>
+</itemizedlist>
+</para>
+
+<para>
+The type families are:
+<itemizedlist>
+<listitem><para>
+<literal>GetResult r f</literal>, the type of the field
+<literal>f</literal> in datatype <literal>r</literal>; and
+</para></listitem>
+<listitem><para>
+<literal>SetResult r f u</literal>, the record type that results from
+setting the field <literal>f</literal> of datatype
+<literal>r</literal> to a value of type <literal>u</literal>.
+</para></listitem>
+</itemizedlist>
+</para>
+
+<para>
+For example, the following datatype would give rise to these instances
+(although the instances do not actually exist, but are created as
+needed by the constraint solver):
+</para>
+
+<programlisting>
+data T a = MkT { foo :: [a] }
+
+type instance GetResult (T a) "foo" = [a]
+type instance SetResult (T a) "foo" [b] = T b
+instance t ~ [a] => Has (T a) "foo" t
+instance Upd (T a) "foo" [b]
+</programlisting>
+
+<para>
+The <literal>getField</literal> and <literal>setField</literal>
+methods of the <literal>Has</literal> and <literal>Upd</literal>
+classes allow polymorphic field lookup and update without requiring a
+datatype containing the field to be in scope. Their types are:
+</para>
+
+<programlisting>
+getField :: Has r f t => proxy f -> r -> t
+setField :: Upd r f u => proxy f -> r -> u -> SetResult r f u
+</programlisting>
+
+<para>
+The proxy arguments enable the field name to be specified. The return
+type of <literal>setField</literal> uses the
+<literal>SetResult</literal> type family to allow type-changing
+update. With the definition of <literal>T</literal> above, the
+following is accepted:
+</para>
+
+<programlisting>
+a :: T Bool
+a = MkT [True]
+
+b :: T Int
+b = setField (Proxy :: Proxy "foo") a [3]
+</programlisting>
+
+<para>
+Type-changing update allows a type parameter of a record datatype to
+be changed provided:
+<itemizedlist>
+<listitem><para>
+It is not 'fixed', i.e. it does not occur in the type of a different
+field of a relevant data constructor (one that has the field being
+updated).
+</para></listitem>
+<listitem><para>
+It occurs in the type of the field being updated. This means that
+'phantom' parameters may not be changed.
+</para></listitem>
+<listitem><para>
+At least one of the variable's occurrences in the field type is
+'rigid' (not under a type family).
+</para></listitem>
+</itemizedlist>
+</para>
+
+<para>
+For example:
+</para>
+
+<programlisting>
+type family Goo x
+data T a b c d = MkT { foo :: (a, Goo b, d, Goo d), bar :: a }
+</programlisting>
+
+<para>
+Here, an update to <literal>foo</literal> must:
+<itemizedlist>
+<listitem><para>
+keep <literal>a</literal> the same, since it occurs in the type of
+<literal>bar</literal>;
+</para></listitem>
+<listitem><para>
+keep <literal>b</literal> the same, since it occurs only under a type
+family; and
+</para></listitem>
+<listitem><para>
+keep <literal>c</literal> the same, since it does not occur in the
+type of <literal>foo</literal>.
+</para></listitem>
+</itemizedlist>
+However, it may change <literal>d</literal>.
+</para>
+
+</sect2>
+
+</sect1>
+
<sect1 id="other-type-extensions">
<title>Other type system extensions</title>