Pattern and Guard Extensions

As of March 2020, School of Haskell has been switched to read-only mode.

These extensions enhance Haskell’s patterns and guards.

NPlusKPatterns

Available in: GHC 6.12 and later

The deprecated extension NPlusKPatterns was originally part of Haskell 98, but has since been removed in Haskell 2010. It allows a very specific extension of pattern syntax, such that you can write, e.g.:

myFunc (x+3) = f x

and have it mean exactly:

myFunc x' | x' >= 3 = let x = x'-3 in f x

In place of 3, you can have any fixed positive integer literal (the “k” in the term “n-plus-k pattern”).

Because of the extremely limited utility of this feature, and its nature as an explicit special case, n-plus-k patterns were eventually deemed unworthy of inclusion in the language and were dropped in the Haskell 2010 Report.

WARNING: Do not use NPlusKPatterns in new code! I only include it here because it may be important when trying to understand legacy Haskell codebases. If NPlusKPatterns seems like it would have been a perfect fit for your use case, try using ViewPatterns instead.

BangPatterns

Available in: All recent GHC versions

The BangPatterns extension allows a small extension to pattern syntax. You can now write patterns of the form !p, where p is any other pattern, and have it mean “match as though by p, but first evaluate the value being matched to weak head normal form.” In essence, just as standard Haskell allows you to force a pattern match to be lazy (or “irrefutable”) by using ~, the BangPatterns extension allows you to force a pattern match to be strict (or “immediate”) by using !.

Try it out!

{-# LANGUAGE BangPatterns #-}

lazyFunc, strictFunc :: () -> ()

-- lazy, even though it would normally be strict
lazyFunc ~() = ()

-- strict, even though it would normally be lazy
strictFunc !v = ()

-- replace 'lazyFunc' with 'strictFunc' and see what happens
main = print $ lazyFunc undefined

ViewPatterns

Available in: All recent GHC versions

The ViewPatterns extension adds a new form of pattern that can be used anywhere any other pattern can, and that applies an arbitrary expression (of appropriate function type), called a view, to the argument before matching. A view pattern e -> p applies the view e to the argument that would be matched by the whole view pattern, and then matches the pattern p against e’s result. The whole view pattern matches against the original value exactly when p matches against the derived value. A consequence of these semantics is that e -> p is irrefutable exactly when p is irrefutable, as long as e is sufficiently lazy.

Try it out!

{-# LANGUAGE ViewPatterns #-}

eitherEndIsZero :: [Int] -> Bool
eitherEndIsZero (head -> 0) = True
eitherEndIsZero (last -> 0) = True
eitherEndIsZero          _  = False

main = print $ eitherEndIsZero [0,1,8,9]

ViewPatterns as a Replacement for NPlusKPatterns

You can use ViewPatterns to replace the deprecated NPlusKPatterns by simply using subtract k as the view. Although it is somewhat longer to type, it affords greater future-proofing and less fragility than NPlusKPatterns does:

myFunc x'@(subtract 3 -> x) | x' >= 3 = f x

PatternGuards

Available in: All recent GHC versions

The PatternGuards extension, now officially incorporated into the Haskell 2010 language, expands guards to allow arbitrary pattern matching and condition chaining. The existing syntax for guards then becomes a special case of the new, much more general form.

You start a guard in the same way as always, with a |. You then continue with either a boolean guardlet or a pattern guardlet. A boolean guardlet is any expression whose type is Bool, and they function as before. A pattern guardlet is of the form p <- e, where p is an arbitrary pattern and e is an arbitrary expression, and which is fullfilled exactly when e matches against p. You may then add additional boolean or pattern guardlets, seperated from each other by commas. Finally, you finish off the complete guard with =, as usual.

PatternGuards syntax is deliberately designed to be reminicent of list comprehension syntax, but be aware that, in a pattern guardlet, p matches against the same type as e has, unlike in a list comprehension generator. All the provided guardlets are tried in order, and only if all of them succeed is that guard’s clause chosen. Otherwise, evaluation moves to the next clause down, as usual (or unceremoniously falls off the bottom if there is no next clause, also as usual).

Try it out!

{-# LANGUAGE PatternGuards #-}
import Data.List (nub)

strangeOperation :: [Int] -> Ordering
strangeOperation xs | 7  <- sum xs
                    , n  <- length xs
                    , n  >= 5
                    , n  <= 20
                      = EQ
                    | 1  <- sum xs
                    , 18 <- length xs
                    , r  <- nub xs `compare` [1,2,3]
                    , r /= EQ
                      = r
                    | otherwise
                      = [3,1,2] `compare` xs

main = print $ strangeOperation ([5,7..21] ++ [20,19..4])

PatternSignatures

Available in: All recent GHC versions

The deprecated extension PatternSignatures allowed type signatures to be put on patterns; it has been wholly subsumed by the much cleaner and more powerful ScopedTypeVariables extension, and should not be used in new code.

PatternSynonyms

Available in: GHC 7.8 and later

In standard Haskell, you can abstract over values and functions, as well as over types and type constructors, but you can’t abstract over patterns. The PatternSynonyms extension fixes this, and gives you a very powerful mechanism for implementation hiding while still providing your module’s users with the streamlined interface that they would get from a fully open implementation. In short, PatternSynonyms lets you present even the most complicated multiply-nested and hyperoptimized data representation with whatever simple constructors-and-pattern-matching-based interface you feel is appropriate.

In order to acheive this, PatternSynonyms enables a new form for top-level declarations in a Haskell module: pattern synonym declarations, which always begin with the new keyword pattern. Pattern synonym declarations produce new patterns that are valid anywhere you can do pattern matching.

There are three styles of pattern synonym declaration; which one you should use depends on exactly how you want the resulting pattern synonym to behave. A monodirectional pattern synonym declaration produces a pattern synonym that is only valid in patterns. For example:

-- This pattern synonym will match the first and third elements of a 3-tuple
-- and bind them to 'x' and 'y' respectively.
pattern OneAndThree x y <- (x, _, y)

-- Here is how the above pattern synonym might be used:
OneAndThree one three = (4, 5, 6)
-- Now 'one' is bound to '4' and 'three' is bound to '6'.

Although the OneAndThree pattern is irrefutable, pattern synonyms in general don’t have to be; for example:

-- This pattern will fail if the list it matches against
-- isn't exactly of the form it's expecting.
pattern VerySpecificList a b c <- [(0, a), (b, "hello"), (1, [_]), c]

-- Here is how the above pattern synonym might be used:
extractMyData :: [(Int, String)] -> Maybe (String, Int, Int, String)
extractMyData (VerySpecificList str0 n0 (n1, str1)) = Just (str0, n0, n1, str1)
extractMyData _ = Nothing

The syntax of a monodirectional pattern synonym declaration is pretty straightforward. You start with the keyword pattern, then give the name of the pattern synonym you’re declaring. Pattern synonyms share a namespace and naming rules with data constructors; that is, you cannot have a pattern synonym and a data constructor with the same name in the same module, and pattern synonyms must either begin with an uppercase letter and continue alphanumerically or begin with a colon and continue symbolically, just like data constructors do.

After the pattern synonym’s name, you give a series of variable names (which might be empty, as in pattern SingleOne <- [1]). You cannot duplicate any of the names in this list; these are your pattern variables.

You follow the pattern variables with the symbol <-, and then finish the declaration with the underlying pattern. This is what your pattern synonym is actually a synonym for; in other words, the underlying pattern is what your pattern synonym will “expand into,” just like type synonyms “expand into” their definitions. In order to guarantee that this makes sense at all, though, there are some restrictions on the form of the underlying pattern: you must use all of your pattern variables exactly once, and any additional variables present must be used at most once. (That last part means we could rewrite our OneAndThree example as pattern OneAndThree x y <- (x, z, y) and still have it behave the same way.) Otherwise, anything that’s a valid pattern elsewhere in the language is a valid underlying pattern for a pattern synonym; this includes pattern extensions like ViewPatterns, BangPatterns, and NPlusKPatterns, as well as previously defined pattern synonyms.

As previously mentioned, monodirectional pattern synonyms can only be used in pattern matching; if you try to use one as part of an expression, GHC will produce an error (at compile time). This is in contrast to bidirectional pattern synonyms, which can be used in both contexts. For example:

pattern NestedTriple x y z = (x, (y, z))

-- Using the above pattern synonym in a pattern context:
NestedTriple x y z = (1, (2, 3))

-- Using the same pattern synonym in an expression context:
t = [NestedTriple 'a' 'b' 'c', ('d', ('e', 'f'))]

Bidirectional pattern synonym declarations take advantage of the fact that Haskell’s pattern and expression syntaxes are (deliberately) very similar to each other. Notionally, a bidirectional pattern synonym “expands into the same thing,” regardless of whether that “thing” is required to be a pattern or an expression; the resulting abstract syntax is then automatically interpreted appropriately for the context it’s found in. This behaviour is a lot like that of data constructors, which can themselves be viewed as bidirectional pattern synonyms that the language just so happens to provide “primitively.”

The only difference in appearance between monodirectional and bidirectional pattern synonym declarations is that the latter uses the symbol = where the former uses <-; the only additional restrictions on bidirectional pattern synonyms compared to monodirectional pattern synonyms are that the underlying pattern must also be valid as an expression, and that you cannot use any “extra“ variables besides the pattern variables.

However, sometimes those additional restrictions are unacceptable; you might have an underlying pattern that is conceptually usable in a bidirectional fashion, but that you cannot express in a way that is syntactically uniform between pattern and expression contexts. In such cases, as well as when you simply what more fine-grained control over the exact behavior of a bidirectional pattern synonym, you can use an explicitly bidirectional pattern synonym declaration. For example:

pattern DoubleHead x <- (x:_):_ where
    DoubleHead x = [[x]]

This pattern will successfully match any non-empty list of lists whose first element is also non-empty, binding its first element; it’s also usable in an expression context, where it will produce a singleton list of a singeton list of its sole parameter.

To declare a bidirectional pattern synonym explicitly, you declare the behavior of the pattern synonym in a pattern context as though you were declaring a monodirectional pattern synonym (i.e., using <- rather than =), but continue after the underlying pattern with the keyword where and an appropriately indented value or function declaration, except that the “value” or “function” that you’re declaring is the pattern synonym as it behaves in an expression context.

It should be noted that the sensibility of explicitly bidirectional pattern synonym declarations is very much in the hands of the programmer; all GHC cares about is that the syntax and types all check successfully. If you want to produce a pattern synonym whose behaviour in an expression context doesn’t “match” its behaviour in a pattern context, you are free to do so! Just make sure your users know what they’re getting into, or else you’ll severely break their expectations about how patterns are “supposed to work.”

Pattern Synonym Type Signatures

In GHC 7.10 and later, pattern synonyms can be given type signatures.

TODO

Pattern Synonyms in Export Lists

TODO

Use Case: Simple API, Complex Implementation

TODO

Use Case: Regex-By-Pattern

TODO

comments powered by Disqus