I'm trying to get a sense of the relationship between view patterns and pattern guards in GHC. Pattern guards seem quite intuitive, while view patterns seem a bit confusing. It kind of looks like view patterns are better for dealing with things deep in a pattern, while pattern guards can reuse a view more intuitively, but I don't quite get it.
View patterns have significant overlap with pattern guards. The main advantage of view patterns is that they can be nested, and avoid introducing intermediate pattern variables. For a silly example:
endpoints (sort -> begin : (reverse -> end : _)) = Just (begin, end)
endpoints _ = Nothing
The pattern guard equivalent requires every new view to bind a new pattern variable, alternating between evaluating expressions and binding patterns.
endpoints xs
| begin : sorted <- sort xs
, end : _ <- reverse sorted
= Just (begin, end)
| otherwise = Nothing
View patterns can also use only those variables bound earlier in the pattern, but it does look nice:
nonzero :: (a -> Int) -> a -> Maybe a
nonzero f (f -> 0) = Nothing
nonzero _ x = Just x
-- nonzero (fromEnum . not . null) "123" == Just "123"
-- "" == Nothing
The main advantage of pattern guards is that they are a simple generalisation of guards, and can include ordinary Boolean expressions. I generally prefer them over view patterns because I find the style of case
and guards less repetitious than the equational style.
View patterns let you project a value before pattern matching on it. It can almost be thought of as a short cut for
foo x = case f x of
...
There's a bit of sugar on top for dealing with more complex views, but basically that's it. On the other hand, pattern guards are strictly more general,
- They can include arbitrary boolean conditions for matching
- They can match using more than one of the variables
I favor view patterns when I'm doing something "lens-like". I have a big piece of data and I'm interested in one particular view of it. For example, with lens
foo (view someLens -> Bar baz quux) = ...
Pattern guards tend to work well when you want something closer to a more flexible case expression.
I'm not sure about pattern guards but I just learned how to use View patterns.
I found this helpful https://blog.ocharles.org.uk/posts/2014-12-02-view-patterns.html but most of the other documentation was next to useless
I had a type like this
data DiffInformation {
firstField :: [Int]
, secondField :: [Int]
, thirdField :: [Int]
}
and a function that was doing this
doStuff :: DiffInformation -> SomeState -> SomeState
doStuff (DiffInformation [] [] []) state = state
doStuff DiffInformation{..} state = undefined -- something more interesting
I needed to add a HashMap to DiffInformation but didn't want to move away from a pattern match and add a case statement/indent everything in the more interesting case.
You don't have access to HashMap's constructor to pattern match and so you have to use a function call to tell whether a HashMap is empty. That function is null :: HashMap k v -> Bool
So I did this
data DiffInformation {
firstField :: [Int]
, secondField :: [Int]
, thirdField :: [Int]
, newField :: HashMap Int String
}
doStuff :: DiffInformation -> SomeState -> SomeState
doStuff (DiffInformation [] [] [] (HashMap.null -> True) state = state
doStuff DiffInformation{..} state = undefined -- something more interesting
You can name the parameter if you need to as well, although it isn't useful in this example
doStuff (DiffInformation [] [] [] (\theNewField -> HashMap.null theNewField -> True) state = state
© 2022 - 2025 — McMap. All rights reserved.