Search code examples
typesf#integersubdomain

Defining a non zero integer type in F#


How I can define a non zero integer in F# that rise a compile time error when assigning zero value?

My question comes by watching this Scott Wlaschin video https://www.youtube.com/watch?v=E8I19uA-wGY&t=960s in minute 16:00

I have found another answers to this question in SO but all refers to a dynamic checking(throwing exception at creation time) but this approach is not a big deal and can be done in any OO and not OO languages. What I'm looking is something like: type NonZeroInteger = int except 0 or something like that.


Solution

  • In F# there aren't really compile time contracts for what you want to do. The F# way to deal with this would be to have a type NonZeroInteger with a private constructor and a function that returns an Option<NonZeroInteger>. This will assure that you never have Some(0).

    What this does is basically forcing the developer who uses your code to account for the possibility that he might not have a NonZeroInteger after constructing one, when given the wrong integer value.

    In your code you can then always safely assume that NonZeroIntegers are in fact non-zero.

    open Option
    
    module A =
        type NonZeroInteger =
            private | NonZeroInteger of int
    
            static member Create (v : int) : Option<NonZeroInteger> =
                if v = 0 then
                    None
                else
                    Some(NonZeroInteger(v))
    
            member this.Get : int =
                this |> fun (NonZeroInteger v) -> v
    
            member this.Print =
                this |> fun (NonZeroInteger v) -> printfn "%i" v
    
    
    printfn "%A" (A.NonZeroInteger(0)) // error FS1093: The union cases or fields of the type 'NonZeroInteger' are not accessible from this code location
    
    let wrong = A.NonZeroInteger.Create(0) // None
    let right = A.NonZeroInteger.Create(-1) // Some
    
    wrong |> Option.iter (fun x -> x.Print) // Doesn't print anything
    right |> Option.iter (fun x -> x.Print) // Prints -1
    

    The private constructor prevents anyone outside your module to construct a NonZeroInteger without going through your Create function.

    This makes the code quite verbose and slow, but safe. So there's definitely a tradeoff here.