Opened 23 months ago

Last modified 7 days ago

#8582 new feature request

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 (15)

comment:1 Changed 15 months 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 7 months 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 7 months ago by blamario (previous) (diff)

comment:3 Changed 4 months ago by cactus

  • Keywords PatternSynonyms added

comment:4 Changed 3 months ago by mpickering

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

comment:5 Changed 2 months ago by mpickering

  • Owner changed from cactus to mpickering

comment:6 Changed 8 weeks ago by mpickering

  • Differential Rev(s) set to Phab:D1152

comment:7 Changed 6 weeks ago by simonpj

  • Milestone set to 7.12.1
  • Priority changed from normal to high

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

comment:8 Changed 4 weeks ago by thoughtpolice

  • Milestone changed from 7.12.1 to 8.0.1

Milestone renamed

comment:9 Changed 3 weeks 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 3 weeks 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 3 weeks 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 follow-up: Changed 3 weeks 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 3 weeks 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 7 days 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 7 days 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.

Note: See TracTickets for help on using tickets.