Pattern Synonyms
Most language entities in Haskell can be named so that they can be abbreviated instead of written out in full. This proposal provides the same power for patterns.
Motivating example
Here is a simple representation of types
data Type = App String [Type]
Using this representations the arrow type looks like App "->" [t1, t2]. Here are functions that collect all argument types of nested arrows and recognize the Int type:
collectArgs :: Type -> [Type] collectArgs (App "->" [t1, t2]) = t1 : collectArgs t2 collectArgs _ = [] isInt (App "Int" []) = True isInt _ = False
Matching on App directly is both hard to read and error prone to write.
The proposal is to introduce a way to give patterns names:
pattern Arrow t1 t2 = App "->" [t1, t2] pattern Int = App "Int" []
And now we can write
collectArgs :: Type -> [Type] collectArgs (Arrow t1 t2) = t1 : collectArgs t2 collectArgs _ = [] isInt Int = True isInt _ = False
Furthermore, pattern synonyms can also be used in expressions, e.g.,
arrows :: [Type] -> Type -> Type arrows = foldr Arrow
Simple pattern synonyms
The simplest form of pattern synonyms is the one from the examples above. The grammar rule is:
pattern conid varid1 ... varidn = patexp
where patexp is the intersection of the grammars for patterns and expression, i.e., those terms that are valid both as a pattern and as an expression.
- Each of the variables on the left hand side must occur exactly once on the right hand side, and these are the only variables that can occur on the right hand side.
- Pattern synonyms are not allowed to be recursive. Cf. type synonyms.
- The semantics is simply expansion of the synonym.
Pattern synonyms can be exported and imported by mentioning the conid in the export/import list. Note that this suffers from the same constructor vs type confusion that already exists in a hiding list, i.e., given the mention of a conid you cannot tell if it refers to a constructor or a type.
You may also give a type signature for a pattern, but as with most other type signatures in Haskell it is optional:
pattern conid :: type
E.g.
pattern Arrow :: Type -> Type -> Type pattern Arrow t1 t2 = App "->" [t1, t2]
Pattern only synonyms
Simple patterns synonyms are restricted to having a right hand side that is also a valid expression. Pattern only synonyms can have any pattern on the right hand side, but may only be used in patterns.
pattern conid varid1 ... varidn = pat
Again, each of the variables on the left hand side must be mentioned exactly once on the right hand side, but now the right hand side can mention other variables as well. These variables will not be bound when using the pattern synonym.
Examples:
pattern ThirdElem x = _:_:x:_ pattern LazySecond a b ~ (a, ~b) third (ThirdElem a) = a third _ = error "No third" fcn :: (Int, (Int, Int)) fcn (LazySecond x (y, z)) = if x == 0 then 0 else y+z
And their expansions
third (_:_:a:_) = a third _ = error "No third" fcn :: (Int, (Int, Int)) fcn (x, ~(y, z)) = if x == 0 then 0 else y+z
Together with ViewPatternsAlternative we can now create patterns that look like regular patterns to match on existing (perhaps abstract) types in new ways.
pattern Plus1 n = n1 | let n = n1-1, n >= 0 fac 0 = 0 fac (Plus1 n) = (n+1) * fac n
Note that the right hand side of Plus1 binds n1 and n, but since only n is mentioned on the left hand side it is the only variable that gets bound when Plus1 is used.
Bidirectional pattern synonyms
What if you want to use Plus1 from the earlier example in an expression? It's clearly impossible since its expansion is a pattern that has no meaning as an expression. Nevertheless, if we want to make what looks like a constructor for a type we will often want to use it in both patterns and expressions. This is the rationale for the most complicated synonyms, the bidirectional ones. They provide two expansions, one for patterns and one for expressions.
pattern conid varid1 ... varidn = pat where cfunlhs rhs
where cfunlhs is like funlhs, except that the functions symbol is a conid instead of a varid.
Example:
pattern Plus1 n = n1 | let n = n1-1, n >= 0 where
Plus1 n = n + 1
The first part as is before and describes the expansion of the synonym in patterns. The second part describes the expansion in expressions.
fac 0 = 0 fac (Plus1 n) = Plus1 n * fac n
Associated Patterns Synonyms
Just like data types and type synonyms can be part of a class declaration, it would be possible to have pattern synonyms as well.
Example:
class ListLike l where
pattern Nil :: l a
pattern Cons :: a -> l a -> a
isNil :: l a -> Bool
isNil Nil = True
isNil (Cons _ _) = False
append :: l a -> l a -> l a
instance ListLike [] where
pattern Nil = []
pattern Cons x xs = x:xs
append = (++)
headOf :: (ListLike l) => l a -> Maybe a
headOf Nil = Nothing
headOf (Cons x _) = Just x
