简体   繁体   中英

Function templates in F#

Let's say I am solving a particular problem and come up with a function

let function parameter1 ... = 
     a lot of adding, multiplying & so on with a lot of 
     literals all over the place

Now this function works fine if my parameters are of type int. But somewhere I will need it to go up to 11, I will need that extra push into int64 or even BigInteger. So what do I do? I copy&paste the function, change the name, and go hunting for all literal appearances that make the compiler think the function should operate on int. And this sucks.

Is there a way to do this:

let greatFunction param1 param2 = (param1+1)/(param2*2)

where param1 and param2 can be any combo of integer types?

Edit:

Expanding a bit on a great tip by kvb below, I came up with the following

module NumericLiteralG 

  let inline FromZero() = LanguagePrimitives.GenericZero
  let inline FromOne() = LanguagePrimitives.GenericOne
  let inline FromInt32 n =
    let rec loop nIn nOut = 
        if nIn>0 then loop (nIn - 1) (nOut + LanguagePrimitives.GenericOne)
        else nOut
    loop n LanguagePrimitives.GenericZero

so it becomes a bit less ugly when used

let inline halfSquare num =
   let res = num / 2G
   res * res

let solve1 = halfSquare 5I 
let solve2 = halfSquare 5.0
let solve3 = halfSquare 5uy

Now the question is should I use this? If not, why not?

I think Generic Arithmetic is common problem in .NET languages. There are many articles explaining different approaches and very soon I will post another one explaining mine which is similar to the solution you posted.

Now, if you ask me if should you use it, I would say: as long as you understand what you are doing why not? I'm using it partially in production and have no issues at all, but because I care about run-time performance I use overloading to resolve everything at compile time. Then to speed up compile time I redefine basic math operators to operate in the same type, otherwise type signatures get really complicated and may take ages to compile.

There are more things to consider but for your specific problem here is a sample code:

open System.Numerics

type FromInt = FromInt with
    static member ($) (FromInt, _:sbyte     ) = fun (x:int) -> sbyte      x
    static member ($) (FromInt, _:int16     ) = fun (x:int) -> int16      x
    static member ($) (FromInt, _:int32     ) = id
    static member ($) (FromInt, _:float     ) = fun (x:int) -> float      x
    static member ($) (FromInt, _:float32   ) = fun (x:int) -> float32    x
    static member ($) (FromInt, _:int64     ) = fun (x:int) -> int64      x
    static member ($) (FromInt, _:nativeint ) = fun (x:int) -> nativeint  x
    static member ($) (FromInt, _:byte      ) = fun (x:int) -> byte       x
    static member ($) (FromInt, _:uint16    ) = fun (x:int) -> uint16     x
    static member ($) (FromInt, _:char      ) = fun (x:int) -> char       x
    static member ($) (FromInt, _:uint32    ) = fun (x:int) -> uint32     x
    static member ($) (FromInt, _:uint64    ) = fun (x:int) -> uint64     x
    static member ($) (FromInt, _:unativeint) = fun (x:int) -> unativeint x
    static member ($) (FromInt, _:bigint    ) = fun (x:int) -> bigint     x
    static member ($) (FromInt, _:decimal   ) = fun (x:int) -> decimal    x
    static member ($) (FromInt, _:Complex   ) = fun (x:int) -> Complex(float x,0.0)  

let inline fromInt (a:int) : ^t = (FromInt  $  Unchecked.defaultof< ^t>) a

module NumericLiteralG =
    let inline FromZero() =LanguagePrimitives.GenericZero
    let inline FromOne() = LanguagePrimitives.GenericOne
    let inline FromInt32 (i:int)     = fromInt i


// This will reduce the number of types inferred, will reduce compile time too.
let inline (+) (a:^t) (b:^t) : ^t = a + b
let inline (-) (a:^t) (b:^t) : ^t = a - b
let inline (*) (a:^t) (b:^t) : ^t = a * b
let inline (/) (a:^t) (b:^t) : ^t = a / b
let inline (~-) (a:^t) : ^t = -a


let inline halfSquare num =
   let res = num / 2G
   res * res

let solve1 = halfSquare 5I 
let solve2 = halfSquare 5.0
let solve3 = halfSquare 5uy 

// Define more generic math functions.

Here's an article on generic, numeric calculations in F# . In general, you have two options:

  • Static member constraints
  • Global numeric associations (available in F# PowerPack)

...or you can combine these techniques.

In your case, it sounds like static constraints will work.

A simple example from that article:

let inline halfSquare num =
   let res = LanguagePrimitives.DivideByInt num 2
   res * res

One way to do it is combining inline keyword and generic bits from LanguagePrimitives module:

let inline greatFunction param1 param2 = 
    let one = LanguagePrimitives.GenericOne
    let two = one + one
    (param1+one)/(param2*two)

// Usage
let f1 = greatFunction 4 2
let f2 = greatFunction 4L 2L
let f3 = greatFunction 4I 2I

While not ideal, and kind of bypassing your main question, you can add type annotations to force the compiler's hand:

let greatFunction (param1:int64) (param2:int64) : int64 = (param1+1)/(param2*2)

Now of course there are no implicit conversions in F#, so you will need to add L to all numeric literals, but they will show up as compiler errors at least.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM