6.5.4. Duplicate record fields

DuplicateRecordFields
Implies

DisambiguateRecordFields

Since

8.0.1

Allow definition of record types with identically-named fields.

Going beyond DisambiguateRecordFields (see Record field disambiguation), the DuplicateRecordFields extension allows multiple datatypes to be declared using the same field names in a single module. For example, it allows this:

module M where
  data S = MkS { x :: Int }
  data T = MkT { x :: Bool }

Uses of fields that are always unambiguous because they mention the constructor, including construction and pattern-matching, may freely use duplicated field names. For example, the following are permitted (just as with DisambiguateRecordFields):

s = MkS { x = 3 }

f (MkT { x = b }) = b

Field names used as selector functions or in record updates must be unambiguous, either because there is only one such field in scope, or because a type signature is supplied, as described in the following sections.

6.5.4.1. Selector functions

Fields may be used as selector functions only if they are unambiguous, so this is still not allowed if both S(x) and T(x) are in scope:

bad r = x r

An ambiguous selector may be disambiguated by the type being “pushed down” to the occurrence of the selector (see Type inference for more details on what “pushed down” means). For example, the following are permitted:

ok1 = x :: S -> Int

ok2 :: S -> Int
ok2 = x

ok3 = k x -- assuming we already have k :: (S -> Int) -> _

In addition, the datatype that is meant may be given as a type signature on the argument to the selector:

ok4 s = x (s :: S)

However, we do not infer the type of the argument to determine the datatype, or have any way of deferring the choice to the constraint solver. Thus the following is ambiguous:

bad :: S -> Int
bad s = x s

Even though a field label is duplicated in its defining module, it may be possible to use the selector unambiguously elsewhere. For example, another module could import S(x) but not T(x), and then use x unambiguously.

6.5.4.2. Record updates

In a record update such as e { x = 1 }, if there are multiple x fields in scope, then the type of the context must fix which record datatype is intended, or a type annotation must be supplied. Consider the following definitions:

data S = MkS { foo :: Int }
data T = MkT { foo :: Int, bar :: Int }
data U = MkU { bar :: Int, baz :: Int }

Without DuplicateRecordFields, an update mentioning foo will always be ambiguous if all these definitions were in scope. When the extension is enabled, there are several options for disambiguating updates:

  • Check for types that have all the fields being updated. For example:

    f x = x { foo = 3, bar = 2 }
    

    Here f must be updating T because neither S nor U have both fields.

  • Use the type being pushed in to the record update, as in the following:

    g1 :: T -> T
    g1 x = x { foo = 3 }
    
    g2 x = x { foo = 3 } :: T
    
    g3 = k (x { foo = 3 }) -- assuming we already have k :: T -> _
    
  • Use an explicit type signature on the record expression, as in:

    h x = (x :: T) { foo = 3 }
    

The type of the expression being updated will not be inferred, and no constraint-solving will be performed, so the following will be rejected as ambiguous:

let x :: T
    x = blah
in x { foo = 3 }

\x -> [x { foo = 3 },  blah :: T ]

\ (x :: T) -> x { foo = 3 }

6.5.4.3. Import and export of record fields

When DuplicateRecordFields is enabled, an ambiguous field must be exported as part of its datatype, rather than at the top level. For example, the following is legal:

module M (S(x), T(..)) where
  data S = MkS { x :: Int }
  data T = MkT { x :: Bool }

However, this would not be permitted, because x is ambiguous:

module M (x) where ...

Similar restrictions apply on import.