Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 685
hdkrus Avatar asked Aug 11 '17 02:08

hdkrus


People also ask

What is a nonzero integer?

The nonzero integers are [rational] integers other than zero, and thus have positive absolute value; they may be positive or negative numbers.

How do you denote nonzero integers?

Symbols:Z/Non-Zero Integers.

What is the product of zero and a non zero integer?

Hence it is proved that the product of zero and non -zero integer is zero.

Is any non zero value are true?

In Python any non-zero integer value is true; zero is false.


2 Answers

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.

like image 88
CodeMonkey Avatar answered Oct 14 '22 06:10

CodeMonkey


The other answers are pretty idiomatic to F#, but here is a way to make it impossible to construct a zero value (at a slight inconvenience to the caller):

type Digit =
| One = 1
| Two = 2
| Three = 3
| Four = 4
| Five = 5
| Six = 6
| Seven = 7
| Eight = 8
| Nine = 9


type NonZero private(ones, tens, hundreds, thousands, ten_thousands) =    
    static let f (n : Digit) = int n

    member val num = f(ones) + 10 * tens + 100 * hundreds + 1000 * thousands + 10000 * ten_thousands

    new (ten_thousands, thousands, hundreds, tens, ones) = NonZero(ones, f tens, f hundreds, f thousands, f ten_thousands)
    new (thousands, hundreds, tens, ones) = NonZero(ones, f tens, f hundreds, f thousands, 0)
    new (hundreds, tens, ones) = NonZero(ones, f tens, f hundreds, 0, 0)
    new (tens, ones) = NonZero(ones, f tens, 0, 0, 0)
    new (ones) = NonZero(ones, 0, 0, 0, 0)

Here is how you would construct, say, the number 123:

let k = new NonZero(Digit.One, Digit.Two, Digit.Three)

And to retrieve the value:

let l = k.num //l is 123 : int

Hence, it is impossible to pass a zero value to a function with type NonZero -> 'a.

like image 35
Rodrick Chapman Avatar answered Oct 14 '22 07:10

Rodrick Chapman