| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Surjective
Documentation
type Covers a = (a -> a) -> a Source #
When the function given to surjective returns a value, it should call a
covers function specifying a pattern. This pattern represents the subset of
values which are considered "covered" by this case, so we can then make sure
that all the values in the codomain are covered.
This covers function needs to take a pattern and a value (the value being
returned), so we would like to write:
covers {pattern} {value}But unfortunately, patterns are not first-class in Haskell. So instead we write:
covers $ \{pattern} -> {value}And we use TemplateHaskell to replace that call with {value}. So covers
appears to have type (a -> a) -> a.
surjective :: Q (TExp (Covers a -> b)) -> Q (TExp b) Source #
Assert that an expression of type b covers all the values of type a.
Typically, b is a function of the form ... -> a, so we are asserting that
the function is "surjective", meaning that for every value in its ouput type,
there is a combination of input values which will produce it. The idea is
that if you add a new constructor to a data type, the compiler should warn
you that your parser no longer cover all the outputs, in the same way that
-Wincomplete-patterns warns you that your pretty-printer no longer cover
all the inputs.
Here is a parsing function which is missing a case:
parseBool :: String -> Maybe Bool parseBool = \case "true" -> Just True _ -> Nothing
And here is how to annotate the function using surjective so the compiler
warns us about that missing case:
-- Warning: Pattern match(es) are non-exhaustive
-- In a case alternative: Patterns not matched: (Just False)
parseBool :: String -> Maybe Bool
parseBool = $$(surjective
[||\covers -> \case
"true" -> covers $ \(Just True) -> Just True
_ -> covers $ \Nothing -> Nothing
||])That is, to check that an expression is surjective:
- Pick a name, typically "
covers", for the function which asserts that a particular pattern is covered. - For each output expression, choose the pattern which this output is considered to cover.
- This is the weird part: wrap the output expression in a lambda and use the pattern in the binding position. Don't worry about partiality, this is just a syntactic trick to associate the pattern to the output expression. This will be rewritten to just the output expression.
- Wrap your entire implementation in another lambda, using the name you
picked for
coversin the binding position. This is another syntactic trick, that lambda will be eliminated as well. - Wrap that lambda in a typed quotation
[|| ... ||]. Make sure theTemplateHaskelllanguage extension is enabled or you'll get a parse error. - Call
surjectiveon the typed quotation, and splice the result back into your program using$$( ... ). The spliced-in code will contain an unused case expression covering all the patterns given tocovers, so we will get a-Wincomplete-patternswarning if we forgot a case.
Since the transformation is entirely syntactic, surjective can be used to
check other kinds of coverage conditions, not just surjectivity. For example,
here we attempt to list all the values of type Maybe Bool, but we are
missing a case:
listMaybeBools :: [Maybe Bool] listMaybeBools = [Just True, Nothing]
Here is how to annotate the list using surjective so the compiler warns us
about that missing case:
-- Warning: Pattern match(es) are non-exhaustive
-- In a case alternative: Patterns not matched: (Just False)
listMaybeBools :: [Maybe Bool]
listMaybeBools = $$(surjective
[||\covers -> [ covers $ \(Just True) -> Just True
, covers $ \Nothing -> Nothing
]
||])