Functions with generic parameter types

前端 未结 5 1551
傲寒
傲寒 2020-11-27 02:38

I am trying to figure out how to define a function that works on multiple types of parameters (e.g. int and int64). As I understand it, function overloading is not possible

相关标签:
5条回答
  • 2020-11-27 03:18

    Overloading is typically the bugaboo of type-inferenced languages (at least when, like F#, the type system isn't powerful enough to contain type-classes). There are a number of choices you have in F#:

    • Use overloading on methods (members of a type), in which case overloading works much like as in other .Net languages (you can ad-hoc overload members, provided calls can be distinguished by the number/type of parameters)
    • Use "inline", "^", and static member constraints for ad-hoc overloading on functions (this is what most of the various math operators that need to work on int/float/etc.; the syntax here is weird, this is little-used apart from the F# library)
    • Simulate type classes by passing an extra dictionary-of-operations parameter (this is what INumeric does in one of the F# PowerPack libraries to generalize various Math algorithms for arbitrary user-defined types)
    • Fall back to dynamic typing (pass in an 'obj' parameter, do a dynamic type test, throw a runtime exception for bad type)

    For your particular example, I would probably just use method overloading:

    type MathOps =
        static member sqrt_int(x:int) = x |> float |> sqrt |> int
        static member sqrt_int(x:int64) = x |> float |> sqrt |> int64
    
    let x = MathOps.sqrt_int 9
    let y = MathOps.sqrt_int 100L
    
    0 讨论(0)
  • 2020-11-27 03:31

    Yes, this can be done. Take a look at this hubFS thread.

    In this case, the solution would be:

    let inline retype (x:'a) : 'b = (# "" x : 'b #)
    let inline sqrt_int (n:'a) = retype (sqrt (float n)) : 'a
    

    Caveat: no compile-time type checking. I.e. sqrt_int "blabla" compiles fine but you'll get a FormatException at runtime.

    0 讨论(0)
  • 2020-11-27 03:31

    Here's another way using runtime type checks...

    let sqrt_int<'a> (x:'a) : 'a = // '
        match box x with
        | :? int as i -> downcast (i |> float |> sqrt |> int |> box)
        | :? int64 as i -> downcast (i |> float |> sqrt |> int64 |> box)
        | _ -> failwith "boo"
    
    let a = sqrt_int 9
    let b = sqrt_int 100L
    let c = sqrt_int "foo" // boom
    
    0 讨论(0)
  • 2020-11-27 03:35

    Not to take away from the correct answers already provided, but you can in fact use type constraints in pattern matching. The syntax is:

    | :? type ->
    

    Or if you want to combine type checking and casting:

    | :? type as foo ->
    
    0 讨论(0)
  • 2020-11-27 03:39

    This works:

    type T = T with
        static member ($) (T, n:int  ) = int   (sqrt (float n))
        static member ($) (T, n:int64) = int64 (sqrt (float n))
    
    let inline sqrt_int (x:'t) :'t = T $ x
    

    It uses static constraints and overloading, which makes a compile-time lookup on the type of the argument.

    The static constraints are automatically generated in presence of an operator (operator $ in this case) but it can always be written by hand:

    type T = T with
        static member Sqr (T, n:int  ) = int   (sqrt (float n))
        static member Sqr (T, n:int64) = int64 (sqrt (float n))
    
    let inline sqrt_int (x:'N) :'N = ((^T or ^N) : (static member Sqr: ^T * ^N -> _) T, x)
    

    More about this here.

    0 讨论(0)
提交回复
热议问题