3

I created the following operator to help with safe division.

let (/!) a b = 
    if b = 0 then 0 
    else a / b

The problem is that it only works with integers, and I'd like this function to work with any numeric primitive (int, float, decimal, etc...).

I've done some reading on automatic generalization, but it's not quite sinking in, and I'm not sure if that's even the right direction to be heading.

How do I accomplish generalizing this operator?

Thanks,

Joe

Joe
  • 776
  • 7
  • 20

1 Answers1

8

Hi this is a bit of a hidden gem but what you are looking for is this:

let inline (/!) (a : ^a) (b : ^a) : ^a = 
    if b = LanguagePrimitives.GenericZero 
    then LanguagePrimitives.GenericZero 
    else a / b

btw: this has this monster type:

val inline ( /! ) :
  a: ^a -> b: ^a ->  ^a
    when  ^a : equality and  ^a : (static member get_Zero : ->  ^a) and
          ^a : (static member ( / ) :  ^a *  ^a ->  ^a)

(this is why I don't really like to write this in the declaration ;) )

As you can see there is support for generic numeric code, but it's not that often discussed (F# has some type-class like things build in - this is a example, the others are stuff like comparable, etc.)

Tomas wrote a good article about this: Writing generic numeric code

PS you don't need the ^a either - but I kindof like to write the signature - and even if you can do this:

let inline (/!) a b = 
    if b = LanguagePrimitives.GenericZero 
    then LanguagePrimitives.GenericZero 
    else a / b

val inline ( /! ) :
  a: ^a -> b: ^b ->  ^c
    when ( ^a or  ^b) : (static member ( / ) :  ^a *  ^b ->  ^c) and
          ^b : (static member get_Zero : ->  ^b) and  ^b : equality and
          ^c : (static member get_Zero : ->  ^c)

it will do you no good as the real division operators are usually only on one type for both arguments - and as I said: I like to emphazise the types over the argument-names ;)

fun fact

you can get around the GenericZero thing like this:

> let inline (/!) a b = if b = (b-b) then (b-b) else a/b;;

val inline ( /! ) :
  a: ^a -> b: ^b ->  ^b
    when ( ^a or  ^b) : (static member ( / ) :  ^a *  ^b ->  ^b) and
          ^b : (static member ( - ) :  ^b *  ^b ->  ^b) and  ^b : equality

(Just to be safe: you might run into trouble with certain types / numbers here ;) )

Random Dev
  • 51,810
  • 9
  • 92
  • 119
  • 2
    You can clean up the signature by annotating the return type: `let inline (/!) (a : ^a) (b : ^a) : ^a = ...` – Daniel Aug 28 '14 at 16:54
  • Oh, very nice! I really like the way you got around GenericZero. This was very informative. Thank you! – Joe Aug 28 '14 at 17:28
  • Wait, what do you mean by "very special number"? Now you've got me curious. – Joe Aug 28 '14 at 17:30
  • it's not that special - it could cause problems with floating point numbers and self-created numbers where you don't obey the algebraic laws - I think it's safe to use but I just wanted to be a bit cautious there ;) – Random Dev Aug 28 '14 at 17:33
  • That makes sense. I suppose you could modify it to include some epsilon value rounding to help with floats. BTW, what advantage do you get by specifying the function as inline? – Joe Aug 28 '14 at 17:35
  • 1
    it's explained in the Tomas article - it helps you spread the constraints around - without it you will end up having Ints again – Random Dev Aug 28 '14 at 17:38
  • 1
    "Statically resolved type parameters can be used only when writing generic functions that are marked as `inline`." – Random Dev Aug 28 '14 at 17:40