Question

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 in F# (certainly the compiler complains). Take for example the following function.

let sqrt_int = function
    | n:int   -> int (sqrt (float n))
    | n:int64 -> int64 (sqrt (float n))

The compiler of course complains that the syntax is invalid (type constraints in pattern matching are not supported it seems), though I think this illustrates what I would like to achieve: a function that operates on several parameter types and returns a value of the according type. I have a feeling that this is possible in F# using some combination of generic types/type inference/pattern matching, but the syntax has eluded me. I've also tried using the :? operator (dynamic type tests) and when clauses in the pattern matching block, but this still produces all sorts errors.

As I am rather new to the language, I may very well be trying to do something impossible here, so please let me know if there is alternative solution.

Was it helpful?

Solution

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

OTHER TIPS

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.

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.

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

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 ->
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top