Search code examples
design-patternsf#abstract-data-typediscriminated-union

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 and give information on what they're holding, 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), which is also a string. 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 over the current codebase (replacing an existing single-case union with a new single-case union over many lines of code is much easier than with a multi-case union).

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 tryCreate uri = Some <| VerifiedUri uri  // or 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)?


EDIT 2019-12-10: this issue is now being discussed for inclusion in F# as a language feature. Vote it up if you think it should be in:).


Solution

  • In a fairly general sense, I think that the pattern you are describing is abstract data type - this is not a name for the specific F# implementation, but it fitst your description quite well.

    To quote Programming with Abstract Data Types by Barbara Liskov and Stephen Zilles in 1974:

    An abstract data type defines a class of abstract objects which is completely characterized by the operations available on those objects. This means that an abstract data type can be defined by defining the characterizing operations for that type.

    In your example, you are defining an abstract data type VerifiedUrl which is described by three operations. The operations create (or tryCreate) create a a value of the abstract data type and the operation get allows you to get the value. The operations that create the value also capture the fact that you can only create a VerifiedUrl from a valid URL string.

    This pattern is perhaps somewhat more focused on the fact that you are hiding the implementation details and exposing only certain operations for manipulating it - while in your case, another important fact is that the values of the abstract data type satisfy certain properties - but you could view those as invariants about the abstract data type. I cannot think of a better established concept to capture this idea.