exhaustive-1.0.0: src/Control/Exhaustive.hs
{-|
`exhaustive` is a library that guarantees that when building a parser, or some
other computation that produces data, /all/ possible constructors in a data type
are considered. You can think of this library as providing a symmetry to GHC's
built in @-fwarn-incomplete-patterns@ compile time warning, although this
library is stricter in that it produces compile time errors if a constructor is
omitted.
Usage of this library is intended to be straightforward, though admittedly the
types might have you think the opposite! To understand this library, an example
may be helpful.
To begin with, consider a simple data type for a "boolean expressions" language:
@
import qualified "GHC.Generics" as GHC
data Expr
= ETrue
| EFalse
| EIf Expr Expr Expr
deriving ('Eq', GHC.'GHC.Generic')
instance 'Generic' Expr
@
Note that we have to make our data type an instance of both
"GHC.Generics".'GHC.Generics.Generic' /and/ "Generics.SOP".'Generic', though this only requires
boiler-plate code.
Next, we would like to build a parser for this language. Let's assume that we
have access to a @parsec@-like library, where we have one basic combinator:
* @symbol :: 'String' -> Parser 'String'@
Ordinarily, we would write our parser as
@
parseExpr :: Parser Expr
parseExpr = 'msum' [ETrue '<$' symbol \"True\"
,EFalse '<$' symbol \"False\"
,EIf '<$>' symbol \"if\" '*>' parseExpr
'<*>' symbol \"then\" '*>' parseExpr
'<*>' symbol \"else\" '*>' parseExpr
]
@
However, nothing is making sure that we actually considered all constructors in
@Expr@. We could just as well write
@
parseExpr :: Parser Expr
parseExpr = 'msum' [ETrue '<$' symbol \"True\"
,EFalse '<$' symbol \"False\"]
@
Although this is significantly less useful!
Using @exhaustive@, we can get exhaustivity checks that we are at least
considering all constructors:
@
parseExpr :: Parser Expr
parseExpr = 'produceFirst' '$'
'construct' (\\f -> f '<$' symbol \"True\") ':*'
'construct' (\\f -> f '<$' symbol \"False\") ':*'
'construct' (\\f -> f '<$'> symbol "if" '*>' parseExpr
'<*>' symbol "then" '*>' parseExpr
'<*>' symbol "else" '*>' parseExpr) ':*'
'Nil'
@
As you can hopefully see, @exhaustive@ requires only minimal changes to an
existing parser. Specifically, we need to:
1. Use 'produceFirst' instead of 'msum'
2. Wrap each constructor application with 'construct'.
3. Use the provided constructor function, rather than the named constructors in
the original data type.
-}
module Control.Exhaustive
( -- * Producing data
-- | The following are the main entry points to the API, all providing functionality to produce data.
produceM
, produceFirst
, produceAll
-- * Constructing Data
-- | In order to produce data, you need a way to construct it - once for each constructor in a data type.
, construct
-- * Re-exported
, Generic)
where
import Control.Applicative
import Control.Monad
import Generics.SOP
import Control.Exhaustive.Internal