6.5.8. Record wildcards

RecordWildCards
Implies:DisambiguateRecordFields.
Since:6.8.1

Allow the use of wildcards in record construction and pattern matching.

Record wildcards are enabled by the language extension RecordWildCards. This extension implies DisambiguateRecordFields.

For records with many fields, it can be tiresome to write out each field individually in a record pattern, as in

data C = C {a :: Int, b :: Int, c :: Int, d :: Int}
f (C {a = 1, b = b, c = c, d = d}) = b + c + d

Record wildcard syntax permits a “..” in a record pattern, where each elided field f is replaced by the pattern f = f. For example, the above pattern can be written as

f (C {a = 1, ..}) = b + c + d

More details:

  • Record wildcards in patterns can be mixed with other patterns, including puns (Record puns); for example, in a pattern (C {a = 1, b, ..}). Additionally, record wildcards can be used wherever record patterns occur, including in let bindings and at the top-level. For example, the top-level binding

    C {a = 1, ..} = e
    

    defines b, c, and d.

  • Record wildcards can also be used in an expression, when constructing a record. For example,

    let {a = 1; b = 2; c = 3; d = 4} in C {..}
    

    in place of

    let {a = 1; b = 2; c = 3; d = 4} in C {a=a, b=b, c=c, d=d}
    

    The expansion is purely syntactic, so the record wildcard expression refers to the nearest enclosing variables that are spelled the same as the omitted field names.

  • For both pattern and expression wildcards, the “..” expands to the missing in-scope record fields. Specifically the expansion of “C {..}” includes f if and only if:

    • f is a record field of constructor C.
    • The record field f is in scope somehow (either qualified or unqualified).

    These rules restrict record wildcards to the situations in which the user could have written the expanded version. For example

    module M where
      data R = R { a,b,c :: Int }
    module X where
      import M( R(R,a,c) )
      f a b = R { .. }
    

    The R{..} expands to R{a=a}, omitting b since the record field is not in scope, and omitting c since the variable c is not in scope (apart from the binding of the record selector c, of course).

  • When record wildcards are used in record construction, a field f is initialised only if f is in scope, and is not imported or bound at top level. For example, f can be bound by an enclosing pattern match or let/where-binding. For example

    module M where
      import A( a )
    
      data R = R { a,b,c,d :: Int }
    
      c = 3 :: Int
    
      f b = R { .. }  -- Expands to R { b = b, d = d }
        where
          d = b+1
    

    Here, a is imported, and c is bound at top level, so neither contribute to the expansion of the “..”. The motivation here is that it should be easy for the reader to figure out what the “..” expands to.

  • Record wildcards cannot be used (a) in a record update construct, and (b) for data constructors that are not declared with record fields. For example:

    f x = x { v=True, .. }   -- Illegal (a)
    
    data T = MkT Int Bool
    g = MkT { .. }           -- Illegal (b)
    h (MkT { .. }) = True    -- Illegal (b)