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.
The nonzero integers are [rational] integers other than zero, and thus have positive absolute value; they may be positive or negative numbers.
Symbols:Z/Non-Zero Integers.
Hence it is proved that the product of zero and non -zero integer is zero.
In Python any non-zero integer value is true; zero is false.
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With