2022-04-26

F# GreaterThanZero passing int or decimal

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.
The real scenario involves an interface and it does not allow me to use inline. I haven't mentioned because I created a simplest use case as possible, didn't imagined that using a class and/or an interface makes the difference here.

type IValidationCheck =   
    abstract member Validate: unit -> Result<unit, string>

type NumberIsPositiveCheck (property:string, value) =
    interface IValidationCheck with
        member (*inline*) this.Validate () =  // does not allow me to use "inline"
            if value > LanguagePrimitives.GenericZero then Ok()  // fail to compile: the type IComparable does not have a get_Zero operator
            else Error $"{property} must be greater than zero"

I'm not able to figure out the way to use an inline function inside the class.



No comments:

Post a Comment