I gave a talk on Beyond the Basics with F# at Prairie Dev Con Regina in October 2018. I was approached after the talk by an attendee with an excellent question. The person was looking to use Active Patterns to validate email addresses with regular expressions. We decided to use a complete active pattern. (Note: The expression pattern used is not exhaustive. Don’t use in production.)
let (|ValidEmail|InvalidEmail|) (email : string) = let m = System.Text.RegularExpressions.Regex.Match(email, @"\w+@\w+.\w+") if m.Success then ValidEmail else InvalidEmail
We discussed their use case for the active pattern at which point I decided to show them another way to model a validated email address.
We can use a descriminated union to model our validated email address.
type ValidEmailAddress = ValidEmailAddress of string
We now have a type safe representation of a validated email address. Functions can take a type of
ValidEmailAddress and trust the data.
Our next step is to ensure a
ValidEmailAddress can only be created after passing the validator. I leaned on the book Domain Driven Design Made Functional by Scott Wlaschin. The book has a perfect example of what we are looking for. We modified our discriminated union to have a
private constructor. The private constructor makes it so not just anyone can create a
We can create a module with the same name as our discriminated union. The module of the same name has access to the private constructor. We place our validation function in the module and have it create the
type ValidEmailAddress = private ValidEmailAddress of string module ValidEmailAddress = open System.Text.RegularExpressions let [<Literal>] Pattern = @"\w+@\w+.\w+" let ValidateEmail email = if Regex.IsMatch(email, Pattern, RegexOptions.IgnoreCase) then Some (ValidEmailAddress email) else None
We now create our
ValidEmailAddress by calling the
ValidEmailAddress.ValidateEmail function. Attempting to directly create a
ValidEmailAddress will cause a compile error. We finish with a type safe email address that can be trusted for the lifetime of the instance and can only be created by passing our validator.