Opened 6 years ago

Closed 4 years ago

Last modified 4 years ago

#8582 closed feature request (fixed)

Record syntax for pattern synonyms

Reported by: cactus Owned by: mpickering
Priority: high Milestone: 8.0.1
Component: Compiler Version:
Keywords: PatternSynonyms Cc:
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: #5144 Blocking:
Related Tickets: Differential Rev(s): Phab:D1152
Wiki Page:


Implement syntax for

pattern Cons{car, cdr} = (car, cdr)

which can then be used just like a regular record constructor, so the following are all valid patterns:

Cons (Just x) []
Cons{car = Just x}

Change History (17)

comment:1 Changed 5 years ago by cactus

Now that I am parsing pattern synonym declarations using the pattern parser (using the same trick as used by the data constructor parser), and typechecking a pattern synonym can add deferred bindings, this should at least be somewhat simpler to implement.

comment:2 Changed 5 years ago by blamario

I just ran into this issue. I'm trying to generalize a legacy record type like

data Foo = Foo{bar :: String}


data GenFoo s = GenFoo{bar :: s}

Unfortunately there is no way to provide a backward-compatible interface after this change. I'm hoping with this feature the solution might be as simple as

type Foo = GenFoo String
pattern Foo{bar} = GenFoo bar


Never mind, I have found a different solution:

data GenFoo s = Foo{bar :: s}

with an added compatibility module:

module Compat (Foo, GenFoo(..), module GenModule) where
import GenModule
type Foo = GenFoo String

This wasn't obvious, but it appears to work.

Last edited 5 years ago by blamario (previous) (diff)

comment:3 Changed 4 years ago by cactus

Keywords: PatternSynonyms added

comment:4 Changed 4 years ago by mpickering

I was also thinking this would be nice and would be interested to implement it before 7.12.

comment:5 Changed 4 years ago by mpickering

Owner: changed from cactus to mpickering

comment:6 Changed 4 years ago by mpickering

Differential Rev(s): Phab:D1152

comment:7 Changed 4 years ago by simonpj

Milestone: 7.12.1
Priority: normalhigh

Matthew says he thinks this'll be ready for 7.12 so I'll milestone it as such.

comment:8 Changed 4 years ago by thoughtpolice


Milestone renamed

comment:9 Changed 4 years ago by goldfire

Some idle thought has spurred on some questions about the design of this feature. See a new section on the wiki page.

comment:10 Changed 4 years ago by mpickering

The first example is a bit tricky although I don't think the results are unexpected.

If instead you write

data D = MkD { foo :: Int }
pattern Pat = MkD { foo = 6 }

baz = (Pat) { foo = 5 }

then everything works as expected. Otherwise there is a type error as Pat does not have a field foo. This is similar to one of the examples I posted on the original discussion thread.

comment:11 Changed 4 years ago by simonpj

I'm sorry but I'm honestly not sure what the specification of the new feature is.

The design subsection is helpful, but it uses only one example, and I can't tell what is supposed to happen in general.

Let's try this. In general, a pattern synonym should behave exactly like its expansion. So given

pattern P x = (x, True)

these two functions should behave the same:

f1 (P False) = "yes"
f2 (P True)  = "no"

f2 (False,True) = "yes"   -- Replace (P pat) by its expansion
f2 (True, True) = "no"

(Actually they are not quite the same; see dynamic semantics subsection. But close enough for now.)

But for your proposal, given

pattern Foo{bar, baz} = (bar, baz)

how, exactly should pattern matches on Foo expand? I can't tell.

Another way to get at this question would be to look at Section 3.17 of the langauge definition. What changes, precisely, are required to accommodate pattern synonyms? (It's a deficiency in the current user manual that it does not say.)

To give the idea, here is my first stab at extending 3.17.2. Add an extra item to the list, saying

  • To match a pattern P p1 .. pn against a value, where P is a pattern synonym defined by pattern P x1 .. xn = prhs,
    • first match the pattern prhs against the value, thereby binding x1..xn.
    • and then match p1 agianst x1, p2 against x2, and so on in sequence.

A similar modification to 3.17.3 would be needed.

Now, how would you change those words if your proposal was adopted?


comment:12 Changed 4 years ago by mpickering

I think the confusion here is that pattern synonyms is an undescriptive name for the feature. I prefer to think of (bidirectional) pattern synonyms as data constructors which are not (yet) associated with a particular type.

It is better then to say that instead of "In general, a pattern synonym should behave exactly like its expansion." that "In general, a pattern synonym should behave exactly like the relevant data constructor". For example, a bidirectional prefix pattern synonym should behave like a prefix data constructor, an infix pattern synonym should behave like an infix data constructor and a record pattern synonym should behave like a record data constructor.

When we introduce records the expansion idea falls apart a bit. For normal prefix pattern synonyms there is one way to pattern match and one way to construct (which matches the expansion). With records there are a few ways to pattern match, a few ways to construct and also the possibility to update. This means the syntax has to diverge from the expansion idea as which expansion do we choose?

If we define a synonym P and datatype Q as follows,

pattern MkP :: Int -> Int -> Q
pattern MkP{x, y} = MkQ x1 y1

data Q = MkQ { x1 :: Int, y1 :: Int }

then we expect MkP to behave precisely as MkQ modulo field naming. To be clear these situations are as follows.

  • Construction (MkP 0 0)
  • Construction with record syntax (MkP { x = 0, y = 1 })
  • Matching (foo (MkP c l) = ...)
  • Matching with normal record syntax (foo (MkP {x = l, y = c}) = ...)
  • Matching with field puns (foo (MkP {x, y}) = ...)
  • Updating with record syntax ((MkP 0 0) { x = 1 })
  • Using record selectors (x (MkP 0 0))

For a unidirectional synonym, we define selectors and the matching part but not updates or construction.

Is that clearer? I think the best specification for this feature is in terms of ordinary records as the goal is to get as close as possible to normal record data constructors.

comment:13 in reply to:  12 Changed 4 years ago by goldfire

Replying to mpickering:

pattern MkP :: Int -> Int -> Q
pattern MkP{x, y} = MkQ x1 y1

data Q = MkQ { x1 :: Int, y1 :: Int }

I imagine you meant

pattern MkP{x, y} = MkQ x y

Otherwise, I agree with what you've said above.

comment:14 Changed 4 years ago by simonpj

Matthew, I feel bad about this but I still don't understand the specification. I honestly don't know what it means to say "a pattern synonym should behave like the relevant data constructor". The wiki page does not even give a syntax. I think it may be something like this

patsyndecl ::=  'pattern' con var1 .. varn <- pat
             |  'pattern' con '{' var1 ',' ... ',' varn '}' <- pat
             | ... more for bidirectional ...

where the second line is the new bit. Is that right? Just writing out the syntax would be very helpful.

We need semantics as well as syntax. In comment:11 I tried to give some concrete pointers for what a specification might look like. It has to say

  • What the syntax is
  • What it means to use such a pattern synonym as a constructor
  • What it means to match against such a synonym

I don't think any of this is very hard to do. But until it is done I don't know what the feature is, so it's hard to review the implementation.

On the implementation front, I believe that you are stuck on a particular point. There's a long comment stream on the Phab ticket so I'm not sure what you are stuck on. Can you just identify the sticking point?

comment:15 Changed 4 years ago by mpickering

Simon, I didn't want to write an explicit specification for this patch because it would amount to copying the specification for records. Which bit did you find confusing in the example I gave? In retrospect, the phrase "relevant data constructor" is confusing. So to explain, by "relevant" I mean an isomorphic (normally defined) data constructor. Does the example not make this any clearer?

The problem with the implementation is with the record updates. Pattern synonym builders have required constraints which normal data constructors don't have. When the record update is desugared, it is necessary to provide the dictionaries for these constraints to the builder. My question was, how was I meant to do this. I ended up adding a field to the RecordUpd constructor which carried around the HsWrapper which then applied the dictionaries. More details are to be found in the last comment on the phab ticket.

I don't have much time anymore to work on this ticket but I would be very disappointed it if did make it into GHC 8.0 as I started working on it several months ago.

comment:16 Changed 4 years ago by bgamari

Resolution: fixed
Status: newclosed

Record pattern synonym support has been merged. Moreover, a specification for the implemented syntax can be found on PatternSynonyms/RecordPatternSynonyms.

comment:17 Changed 4 years ago by thomie

commit 2a74a64e8329ab9e0c74bec47198cb492d25affb:

Author: Matthew Pickering <>
Date:   Mon Oct 19 21:17:29 2015 +0100

    Record pattern synonyms
    This patch implements an extension to pattern synonyms which allows user
    to specify pattern synonyms using record syntax. Doing so generates
    appropriate selectors and update functions.
    === Interaction with Duplicate Record Fields ===
    The implementation given here isn't quite as general as it could be with
    respect to the recently-introduced `DuplicateRecordFields` extension.
    Consider the following module:
        {-# LANGUAGE DuplicateRecordFields #-}
        {-# LANGUAGE PatternSynonyms #-}
        module Main where
        pattern S{a, b} = (a, b)
        pattern T{a}    = Just a
        main = do
          print S{ a = "fst", b = "snd" }
          print T{ a = "a" }
    In principle, this ought to work, because there is no ambiguity. But at
    the moment it leads to a "multiple declarations of a" error. The problem
    is that pattern synonym record selectors don't do the same name mangling
    as normal datatypes when DuplicateRecordFields is enabled. They could,
    but this would require some work to track the field label and selector
    name separately.
    In particular, we currently represent datatype selectors in the third
    component of AvailTC, but pattern synonym selectors are just represented
    as Avails (because they don't have a corresponding type constructor).
    Moreover, the GlobalRdrElt for a selector currently requires it to have
    a parent tycon.
    (example due to Adam Gundry)
    === Updating Explicitly Bidirectional Pattern Synonyms ===
    Consider the following
    pattern Silly{a} <- [a] where
      Silly a = [a, a]
    f1 = a [5] -- 5
    f2 = [5] {a = 6} -- currently [6,6]
    === Fixing Polymorphic Updates ===
    They were fixed by adding these two lines in `dsExpr`. This might break
    record updates but will be easy to fix.
    + ; let req_wrap = mkWpTyApps (mkTyVarTys univ_tvs)
    - , pat_wrap = idHsWrapper }
    +, pat_wrap = req_wrap }
    === Mixed selectors error ===
    Note [Mixed Record Field Updates]
    Consider the following pattern synonym.
        data MyRec = MyRec { foo :: Int, qux :: String }
        pattern HisRec{f1, f2} = MyRec{foo = f1, qux=f2}
    This allows updates such as the following
        updater :: MyRec -> MyRec
        updater a = a {f1 = 1 }
    It would also make sense to allow the following update (which we
        updater a = a {f1 = 1, qux = "two" } ==? MyRec 1 "two"
    This leads to confusing behaviour when the selectors in fact refer the
    same field.
        updater a = a {f1 = 1, foo = 2} ==? ???
    For this reason, we reject a mixture of pattern synonym and normal
    record selectors in the same update block. Although of course we still
    allow the following.
        updater a = (a {f1 = 1}) {foo = 2}
        > updater (MyRec 0 "str")
        MyRec 2 "str"
Note: See TracTickets for help on using tickets.