PatternSynonyms

For the 24 days of Haskell Extensions blog this is plagiarised from see here.

Pattern Synonyms are essentially a way of aliasing pattern matches. For example, continuing my silly example habit:

{-# LANGUAGE PatternSynonyms #-}

module Example where


main :: IO ()
main = getChar >>= \c ->
       print (toChar . mirrorLetter . toLetter $ c)


data Letter = A | B | C | D | W |X | Y | Z


toChar :: Letter -> Char
toChar A = 'a'
toChar B = 'b'
toChar C = 'c'
toChar D = 'd'
toChar W = 'w'
toChar X = 'x'
toChar Y = 'y'
toChar Z = 'z'


toLetter :: Char -> Letter
toLetter 'a' = A
toLetter 'b' = B
toLetter 'c' = C
toLetter 'd' = D


mirrorLetter :: Letter -> Letter
mirrorLetter A = Z
mirrorLetter B = Y
mirrorLetter C = X
mirrorLetter D = W

The key thing to notice here is the effort, both in terms of computation and code composition of converting a primitive to an ADT, processing that ADT and then converting it back again.

All of this would be much easier if the computer simply knew that A = 'a' at compile time rather than having to compute a function at run time to achieve the same end.

Pattern synonyms to the rescue!

The same code can be represented much more clearly and tersely using the PatternSynonym extension:

{-# LANGUAGE PatternSynonyms #-}

module Main where


main :: IO ()
main = getChar >>= \c ->
       print $ mirrorLetter c

pattern A = 'a'
pattern B = 'b'
pattern C = 'c'
pattern D = 'd'
pattern W = 'w'
pattern X = 'x'
pattern Y = 'y'
pattern Z = 'z'


mirrorLetter :: Char -> Char
mirrorLetter A = Z
mirrorLetter B = Y
mirrorLetter C = X
mirrorLetter D = W

This is exactly the same program without all the boilerplate guff and far less runtime overhead. Pretty good, yes?

But wait, there’s more!

A pattern synonym can also work with a newtype, so you can create programs that look like this:

{-# LANGUAGE PatternSynonyms #-}

module Main where


newtype Letter = MkLetter { toChar :: Char }

pattern A = MkLetter 'a'
pattern B = MkLetter 'b'
pattern C = MkLetter 'c'
pattern D = MkLetter 'd'
pattern W = MkLetter 'w'
pattern X = MkLetter 'x'
pattern Y = MkLetter 'y'
pattern Z = MkLetter 'z'

main :: IO ()
main = getChar >>= \c ->
       print . mirrorLetter $ MkLetter c


mirrorLetter :: Letter -> Char
mirrorLetter A = toChar Z
mirrorLetter B = toChar Y
mirrorLetter C = toChar X
mirrorLetter D = toChar W
mirrorLetter x = toChar x  

In this case we have created a BiDirectional pattern. What this means is that we have constructed a value in the pattern, and then we have unwrapped it in mirrorLetter to pass it back to the print function.

PatternSynonym seems like a very useful way of reducing boilerplate.

Written with StackEdit.

Comments

Popular Posts