Active Pattern
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.
Domain Driven
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.
Private Validation
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 ValidEmailAddress
instance.
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 ValidEmailAddress
instance.
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.