unrepresentable?

This is a discussion inspired by a tweet of .

Can we really make illegal data unrepresentable?

16b064881d6bfb4769869654280c768f_200

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
            None
        else
            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"

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

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"

Update

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.

Advertisements

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 )

Google+ photo

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

Connecting to %s