Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

It's easy to write a function that adds two ints in F#:

let add x y = x + y

Actually it's the same as:

let add (x:int) (y:int): int = x + y

If you need to make it generic so that it can take arguments of other type than int, you should use inline keyword:

> let inline add x y = x + y;;

val inline add :
  x: ^a -> y: ^b ->  ^c
    when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^c)

Then you can pass values of any type that has the + operator to add:

> add 1 2;;
val it : int = 3
> add 1L 2L;;
val it : int64 = 3L
> add 1I 2I;;
val it : System.Numerics.BigInteger = 3 

However, it's not easy to write a generic function if you have to use literals in the middle of an expression:

> let inline inc x = x + 1;; 

val inline inc : x:int -> int

x always evaluates to be of int since the literal 1 is of int. It would be cool if you could write something like the following pseudo-code:

let inline inc (x:^T) = x + (1 :> ^T)

In order to do so, F# provides NumericLiteralX (where X can be replaced with G, N, Z, ...):

module NumericLiteralG =
    let inline FromZero() = LanguagePrimitives.GenericZero
    let inline FromOne() = LanguagePrimitives.GenericOne
    let inline FromInt32 n =
        let one = FromOne()
        let zero = FromZero()
        let iinc = if n > 0 then 1 else -1
        let ginc = if n > 0 then one else -one

        let rec loop i g = 
            if i = n then g
            else loop (i + iinc) (g + ginc)
        loop 0 zero 

Now you can make the inc function generic as follows:

> let inline inc x = x + 1G;;

val inline inc :
  x: ^a ->  ^c
    when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^c) and
          ^b : (static member get_One : ->  ^b)

The obvious deficiency with NumericLiteralX, however, is FromInt32 can be horribly slow for a large number. For example, if you write a function that divides a number by 1000000:

let inline divideByMillion x = x / 1000000G

the loop in FromInt32 executes 1000000 times!

Here I propose a better approach that has no such performance hit:

type BigIntCast = BigIntCast with
    static member inline (=>) (BigIntCast, x: int) = bigint x
    static member inline (=>) (BigIntCast, x: uint32) = bigint x
    static member inline (=>) (BigIntCast, x: int64) = bigint x
    static member inline (=>) (BigIntCast, x: uint64) = bigint x
    static member inline (=>) (BigIntCast, x: bigint) = x
    static member inline (=>) (BigIntCast, x: single) = bigint x
    static member inline (=>) (BigIntCast, x: double) = bigint x
    static member inline (=>) (BigIntCast, x: decimal) = bigint x
    static member inline (=>) (BigIntCast, x: byte[]) = bigint x

type NumericCast = NumericCast with
    static member inline (=>) (NumericCast, _: sbyte) = sbyte
    static member inline (=>) (NumericCast, _: byte) = byte
    static member inline (=>) (NumericCast, _: int16) = int16
    static member inline (=>) (NumericCast, _: uint16) = uint16
    static member inline (=>) (NumericCast, _: int) = int
    static member inline (=>) (NumericCast, _: uint32) = uint32
    static member inline (=>) (NumericCast, _: int64) = int64
    static member inline (=>) (NumericCast, _: uint64) = int64
    static member inline (=>) (NumericCast, _: nativeint) = nativeint
    static member inline (=>) (NumericCast, _: unativeint) = unativeint
    static member inline (=>) (NumericCast, _: single) = single
    static member inline (=>) (NumericCast, _: double) = double
    static member inline (=>) (NumericCast, _: decimal) = decimal
    static member inline (=>) (NumericCast, _: bigint) = (=>) BigIntCast

let inline (^>) t x = (NumericCast => x) t

Now the inc function can be written as:

> let inline inc x = x + (1 ^> x);;

val inline inc :
  x: ^a ->  ^c
    when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^c) and
         (NumericCast or  ^a) : (static member ( => ) : NumericCast *  ^a ->
                                                          int ->  ^b)   

1 ^> x reads "cast 1 to the same type as x."

The results are the same as before:

> inc 1;;
val it : int = 2
> inc 1L;;
val it : int64 = 2L
> inc 1I;;
val it : System.Numerics.BigInteger = 2

Type safety is enforced by the compiler:

> inc "1";;

  inc "1";;
  ^^^

C:\Users\junyoung\AppData\Local\Temp\stdin(8,1): error FS0043: No overloads match for method 'op_EqualsGreater'. The available overloads are shown below (or in the Error List window).
Possible overload: 'static member NumericCast.( => ) : NumericCast:NumericCast * x: ^t -> ('a -> 'a) when  ^t : (static member get_One : ->  ^t)'. Type constraint mismatch. The type 
    string    
is not compatible with type
    'a    
... snip ...

In summary, it's easy to use, fast and type-safe. Choose any three. :-)

What do you think?

The original idea of using an operator trick came from here.

share|improve this question
    
This has been around for quite some time now, check out github.com/gmpl/FsControl/blob/master/FsControl.Core/Numeric.fs –  Mauricio Scheffer Aug 1 at 12:56

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.