This is a discussion inspired by a tweet of .

Can we really make illegal data unrepresentable?


I’m afraid we can’t, but let’s try

type TrimNonEmptyString internal (value : string) =
    member __.Value = value
    override __.ToString() = value
    override __.Equals(yobj) =
        match yobj with
        | 😕 TrimNonEmptyString as y -> (__.Value = y.Value)
        | _ -> false
    override __.GetHashCode() = hash value
    static member TryParse (value : string) =
        if System.String.IsNullOrWhiteSpace value then
            Some <| value.Trim()     with     interface System.IComparable with         member __.CompareTo yobj =             match yobj with             | 😕 TrimNonEmptyString as y ->
            if __.Value > y.Value then 1
            elif __.Value < y.Value then -1             else 0             | _ -> invalidArg "TrimNonEmptyString" "cannot compare values of different types"

let main argv =
    System.Console.ReadLine() |> ignore;
    let test = TrimNonEmptyString (null)
    printfn "*%A*" test
    System.Console.ReadLine() |> ignore;

Here we go, a NullReferenceException means we failed our goal.

Of course, I’m aware that it was an internal c.tor and there is the below easy fix, but that’s not my point.

    let test = TrimNonEmptyString.TryParse null
    printfn "*%A*" test

Look at this

    let test = TrimNonEmptyString.TryParse null
    if test.IsSome
    then printfn "*%s*" test.Value
    else printf "illegal state"

Again, it doesn’t prevent from a NullReferenceException in case of mistyping

    if not test.IsSome

Well, of course .Value is unsafe and a better approach should be

    match test with
    | Some(s) -> printfn "*%s*" s
    | None -> printf "illegal state"


Notice that the NullReferenceException is due to a bug in F# 4.0, but – aside from this technical detail – we still have two ways to proceed.

type TrimNonEmptyString private (value : string) =
    member __.Value = value
    override __.ToString() = value

The first is to make the c.tor private, but that would lead us into a classical parser, the traditional check method in C#, again imho such is not a type truly making illegal data unrepresentable.

The other option is to make the c.tor available (I’d expect to be able to use the type as input for other functions) and add further checks

type TrimNonEmptyString (value : string) =
    member __.Value =
        if System.String.IsNullOrWhiteSpace value then ""
        else value
    override __.ToString() =
        if System.String.IsNullOrWhiteSpace value then ""
        else value

That’s what I eventually prefer, but we’re still far from a usable business case, at least we should see this type used as a constrained input to another function… Comments are welcomed.

Update 2

I’ve found a nice use case from Domain Type options and converters.

Let’s define a trivial converter in our Domain example.

module Domain =
    let apply xo fo =
         match fo, xo with
         | Some f, Some x -> Some (f x)
         | _ -> None
    let (<*>) f x = apply x f

    let prodLength (s1 : string) (s2 : string) =
        s1.Length * s2.Length

and now the usage is shown in a couple of expecto tests

            testCase "Prod lenght some some" <| fun () ->
                let s = Some prodLength <*> (TrimNonEmptyString.TryParse "asd") <*> (TrimNonEmptyString.TryParse "fsfs")
                Expect.isSome (s) "expected some"
                Expect.isTrue (s = Some (3 * 4)) "expected 12"

            testCase "Prod lenght none some" <| fun () ->
                let s = Some prodLength <*> (TrimNonEmptyString.TryParse null) <*> (TrimNonEmptyString.TryParse "fsfs")
                Expect.isNone (s) "expected none"


Bottom line

Back to classical C# programming. If an integer is your “legal” data, then the type int makes illegal data unrepresentable. If you assign a string to it, the code doesn’t even compile. If you define an input parameter as int, the caller can’t really pass anything else to the function.

This is not a static method parsing a string to check if it represents an integer.

One thought on “unrepresentable?

  1. What I missed here is that – beside the applicative example with the apply function, one can simply use the wrapper type (with private c.tor) at the boundaries of the domain forcing either the persistence or the UI layer (e.g. the ViewModel) to use a match pattern before calling the domain api! So we can achieve the benefits of making illegal states unrepresentable and delegating the validation to the domain!
    Also structural equality should be warranted by a more idiomatic single case DU instead of an OO (C# like) class…


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s