Active Pattern to Domain Driven

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.

Shane Charles
Software Developer