How to write a function for generic numbers?

Building on Brian’s and Stephen’s answers, here’s some complete code:

module NumericLiteralG = 
    let inline FromZero() = LanguagePrimitives.GenericZero
    let inline FromOne() = LanguagePrimitives.GenericOne
    let inline FromInt32 (n:int) =
        let one : ^a = FromOne()
        let zero : ^a = FromZero()
        let n_incr = if n > 0 then 1 else -1
        let g_incr = if n > 0 then one else (zero - one)
        let rec loop i g = 
            if i = n then g
            else loop (i + n_incr) (g + g_incr)
        loop 0 zero 

let inline crossfoot (n:^a) : ^a =
    let (zero:^a) = 0G
    let (ten:^a) = 10G
    let rec compute (n:^a) =
        if n = zero then zero
        else ((n % ten):^a) + compute (n / ten)
    compute n

crossfoot 123
crossfoot 123I
crossfoot 123L

UPDATE: Simple Answer

Here’s a standalone implementation, without the NumericLiteralG module, and a slightly less restrictive inferred type:

let inline crossfoot (n:^a) : ^a =
    let zero:^a = LanguagePrimitives.GenericZero
    let ten:^a = (Seq.init 10 (fun _ -> LanguagePrimitives.GenericOne)) |> Seq.sum
    let rec compute (n:^a) =
        if n = zero then zero
        else ((n % ten):^a) + compute (n / ten)
    compute n

Explanation

There are effectively two types of generics in F#: 1) run-type polymorphism, via .NET interfaces/inheritance, and 2) compile time generics. Compile-time generics are needed to accommodate things like generic numerical operations and something like duck-typing (explicit member constraints). These features are integral to F# but unsupported in .NET, so therefore have to be handled by F# at compile time.

The caret (^) is used to differentiate statically resolved (compile-time) type parameters from ordinary ones (which use an apostrophe). In short, 'a is handled at run-time, ^a at compile-time–which is why the function must be marked inline.

I had never tried to write something like this before. It turned out clumsier than I expected. The biggest hurdle I see to writing generic numeric code in F# is: creating an instance of a generic number other than zero or one. See the implementation of FromInt32 in this answer to see what I mean. GenericZero and GenericOne are built-in, and they’re implemented using techniques that aren’t available in user code. In this function, since we only needed a small number (10), I created a sequence of 10 GenericOnes and summed them.

I can’t explain as well why all the type annotations are needed, except to say that it appears each time the compiler encounters an operation on a generic type it seems to think it’s dealing with a new type. So it ends up inferring some bizarre type with duplicated resitrictions (e.g. it may require (+) multiple times). Adding the type annotations lets it know we’re dealing with the same type throughout. The code works fine without them, but adding them simplifies the inferred signature.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)