As it appears, both answers of @chi and @Sibi are what Refinement Types are about. I.e., the types which enclose other types, while restricting the range of supported values with a validator. The validation can be done both at run-time and compile-time depending on the use-case.
It just so happens that I've authored "refined", a library, which provides abstractions for both cases. Follow the link for an extensive introduction.
To apply this library in your scenario, in one module define the predicate:
import Refined import Data.ByteString (ByteString) data IsEmail instance Predicate IsEmail ByteString where validate _ value = if isEmail value then Nothing else Just "ByteString form an invalid Email" where isEmail = error "TODO: Define me" -- | An alias for convenince, so that there's less to type. type EmailBytes = Refined IsEmail ByteString
Then use it in any other module (this is required due to Template Haskell).
You can construct the values both at compile-time and run-time:
-- * Constructing ------------------------- {-| Validates your input at run-time. Abstracts over the Smart Constructor pattern. -} dynamicallyCheckedEmailLiteral :: Either String EmailBytes dynamicallyCheckedEmailLiteral = refine "[email protected]" {-| Validates your input at compile-time with zero overhead. Abstracts over the solution involving Lift and QuasiQuotes. -} staticallyCheckedEmailLiteral :: EmailBytes staticallyCheckedEmailLiteral = $$(refineTH "[email protected]") -- * Using ------------------------- aFunctionWhichImpliesThatTheInputRepresentsAValidEmail :: EmailBytes -> IO () aFunctionWhichImpliesThatTheInputRepresentsAValidEmail emailBytes = error "TODO: Define me" where {- Shows how you can extract the "refined" value at zero cost. It makes sense to do so in an enclosed setting. E.g., here you can see `bytes` defined as a local value, and we can be sure that the value is correct. -} bytes :: ByteString bytes = unrefine emailBytes
Also please beware that this is just a surface of what Refinement Types can cover. There's actually much more useful properties to them.
RegExpof regular expressions: switch onDataKinds, and now you can write down the type of data valid for a given regexpRegData :: RegExp -> *, and the singleton types for regexpsREGEXP :: RegExp -> *. You'll probably also need a validating parser,regParse :: REGEXP r -> String -> Maybe (RegData r), so you can go from strings to validated data, given the singleton value for the regexp you want. If you can write a regexp recognizer, thenregParseis an evidence-producing refinement of the same idea.