View Patterns and “And Patterns” for Record Patterns

View Patterns

Man oh man, I’m fond of view patterns.

I don’t know when it happened, but I became slightly obsessed with scoping the Haskell I write. I try both to make as few bindings as possible (without obfuscating too much) and to minimize the scope of each binding. I find view patterns crucial for the first one.

This post was prompted by the newest way I found to use view patterns. I had pondered using them to simplify some of my nested record patterns, but I had thought it would interfere with @-patterns. It was a pleasant surprise to realize that changes like the following work just right.

The pattern tds@TDS {rename_ = Rename {ctor_ = ren}}
becomes       tds@(ctor_ . rename_ -> ren).

The improvement here, in my opinion, is that we eliminate the redundancy between the constructors and their field patterns. Of course, multiple constructors in the same datatype can have fields with the same label, so understand that the view pattern is less specific than the record syntax I shared above — unless TDS and Rename are the only constructors of their respective type.

“And Patterns”

If you want multiple fields from a “level” of the pattern, then we need to do some extra plumbing. I suggest

infixr 3 :&, &:
f &: g = \x -> f x :& g x
data x :& y = x :& y


tds@TDS {rename_ = Rename {ctor_ = ren},
         fs_ = fs@FS{targets_ = ots, require_ = req}


tds@(rename_ &: fs_ -> (ctor_ -> ren) :&
                fs@(targets_ &: require_ -> ots :& req))

Even I admit that’s a little dense; it’s unfortunate that the field name and the corresponding pattern get separated. We’d need a pattern combinator akin to &: in order to mitigate that separation, but, without a language extension, that would require metaprogramming. The Template Haskell seems a little out of hand…

(&) :: Q Pat -> Q Pat -> Q Pat
p1 & p2 = viewP [| id &: id |] [p| p1 :& p2 |]

tds@$([p| rename_ -> ctor_ -> ren |] &
      [p| fs@(fs_ -> $([p| targets_ -> ots |] &
                       [p| require_ -> req |])) |])

Also, it seems TH doesn’t currently parse view patterns inside quotations.

The & combinator, though, is pretty nice; I call it an and pattern. The semantics are simply that both of its pattern arguments need to match in order for it to match. Quasiquotation offers another way forward with the hypothetical

tds@[andp| rename_ -> ctor_ -> ren &
           fs_ -> fs@(targets_ -> ots & require_ -> req) |]

where & has really low fixity and corresponds to a use of & as defined above. It’d probably be easiest to write such a customized pattern parser where view patterns are limited to applications of a named function and type ascriptions are disallowed; that way you’re not parsing expressions and types.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s