F# GreaterThanZero passing int or decimal

Viewed 76

I want to create a function that check if the passed value is greater than zero.
The passed value can be an int or a decimal (ideally a "numeric value").

In the immediate I just started with this:

type number =
| I of int 
| D of decimal 

type Checker () =
    member this.Validate value =
        match value with 
        | I x when x > 0 -> "ok"
        | D x when x > 0m -> "ok"
        | _ -> "error"

let a = 1f
let b = 1m
//let a_IsValid = Checker().Validate(a) // does not compile, expect number (not int)
//let b_IsValid = Checker().Validate(b) // does not compile, expect number (not decimal)

Found not immediate to pass a "number" so tried something different... I found this article (http://tomasp.net/blog/fsharp-generic-numeric.aspx/) and I thought "static member constraint" is the perfect solution for me. A basic example works as expected:

let inline divideByTwo value = 
    LanguagePrimitives.DivideByInt value 2

divideByTwo 1f |> ignore
divideByTwo 1m |> ignore

but a different scenario found me very surprised:

type Calculator () =
    let divideByTwo value = 
        LanguagePrimitives.DivideByInt value 2

    member this.DivideByTwo value = 
        LanguagePrimitives.DivideByInt value 2

    member this.ValidateGeneric value =
        match LanguagePrimitives.GenericGreaterThan value 0m with
        | true -> "ok"
        | _ -> "error"

//let half = Calculator().DivideByTwo(1) // DivideByInt does not support int !!

// cannot use both the following, the first one will "force" the type, and the other will not work
let a_half = Calculator().DivideByTwo(1f) // ok if used before the "decimal" version
let b_half = Calculator().DivideByTwo(1m) // ok only if comment the previous one

It seems not to work when I want to use more than one type for the passing value.

More than that, the function I need (GenericGreaterThan) seems to have another "limitation", explained below. The example in the article use DivideByInt and, as the name said, it divide the passed value by an int, a well defined type. LanguagePrimitives.GenericGreaterThan needs 2 parameters, a passed value and a fixed one to compare to. The signature of the function as only one generic type for both, so if you pass a type 'T it expect the second one to be 'T too.
I just wants to compare with zero without passing it, but using "0" forced my value to be an int and using "0m" force the value to be a decimal.

There is a simple way to have a function that check if a "numeric" value is greater than "zero" ? Should I use obj and box it .... or use cast ... or stop trying and just use a different function for every type I need ?

[UPDATE]
I tried to use the LanguagePrimitives.GenericZero as suggested but still not able to have a working solution for my particular scenario.
I created a new issue here: F# - Compare LanguagePrimitives.GenericZero with a value passed on the class contructor .

2 Answers

there lots of things here.

your first example didn't work because you needed to wrap your number inside the type number (I assume you realise this? but didnt want it to work like that?)

type Checker () =
    member this.Validate value =
        match value with 
        | I x when x > 0 -> "ok"
        | D x when x > 0m -> "ok"
        | _ -> "error"

let a = I 1
let b = D 1m
let a_IsValid = Checker().Validate(a)
let b_IsValid = Checker().Validate(b)

your second example is that in doesnt support divide by int? yes what is the value of 1/2? its not an int, so thats correct by design.

the third question seems to be that this code doesnt compile and run?

type Calculator () =
    member inline _.DivideByTwo value = 
        LanguagePrimitives.DivideByInt value 2

let b_half = Calculator().DivideByTwo(1m) // ok for me
let a_half = Calculator().DivideByTwo(1f) // ok for me

but this works for me.

The fourth question appears to be the need to use static constraints to test if something is > 0?

but 0 (as in mathematics) is a different thing in different number systems, its generic too so you need LanguagePrimitives.GenericZero. putting that all together we get

type Calculator () =
    member inline _.DivideByTwo value = 
        LanguagePrimitives.DivideByInt value 2

    member inline _.ValidateGeneric value =
        match LanguagePrimitives.GenericGreaterThan 
            value 
            LanguagePrimitives.GenericZero with
        | true -> "ok"
        | _ -> "error"

let b_half = Calculator().DivideByTwo(1m)
let a_half = Calculator().DivideByTwo(1f)
let y = Calculator().ValidateGeneric(1m)
let z = Calculator().ValidateGeneric(1f)

as for the divide by 1/2 question, you may need to think what you want it to do? really the input type is defined by what output type you want? decimal? float? etc

Comparing against zero generically is actually quite simple. The following function should work for any numeric type:

let inline isPositive x =
    x > LanguagePrimitives.GenericZero

isPositive 1.0 |> printfn "%A"   // true
isPositive 1m |> printfn "%A"    // true

Dividing by two generically is also pretty easy. You just have to define your own generic two, since it's not a built-in primitive:

let inline divideByTwo x =
    let two =
        LanguagePrimitives.GenericOne
            + LanguagePrimitives.GenericOne
    x / two

divideByTwo 5.0 |> printfn "%A"   // 2.5
divideByTwo 4m |> printfn "%A"    // 2
Related