mardi 29 janvier 2019

What's the functional design pattern for a discriminated union with protected creation, and public read access?

Discriminated unions are typically used as data holders, but occasionally I find myself having the need to prevent creation of a discriminated union, but to still be able to pattern match over it using familiar syntax.

For the sake of argument, let's say we represent a URI with a string, but I want to create a type that has a guaranteed validated URI (i.e., it's valid per the RFC). Just using Some/None doesn't work here, as I still want to access any invalid string as well. Also, I like a mild refactoring experience.

I can solve this problem as follows, which I think shows what I intend to do (leaving out the error cases for simplicity):

[<AutoOpen>]
module VerifiedUriModule =
    module VerifiedUri =
        type VerifiedUri = 
            private 
            | VerifiedUri of string

        let Create uri = VerifiedUri uri  // validation and error cases go here

        let Get (VerifiedUri uri) = uri

    let (|VerifiedUri|) x =
        VerifiedUri.Get x

The extra level with the AutoOpen is simply to allow unqualified access of using the active recognizer.

I may end up using a typical Result type, but I was wondering whether this is a typical coding practice, or whether whenever I find myself doing something like this, I should hear a voice in my head saying "rollback, rollback!", because I'm violating classical functional programming principles (am I?).

I realize this is a case of information hiding and it looks much like mimicking OO class behaviors with data. What would be the typical F#'ish approach be (apart from creating a class with a private ctor)?

Aucun commentaire:

Enregistrer un commentaire