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
Post a Comment