views:

2076

answers:

4

Hello,

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.

+7  A: 

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.

For reference, take a look at inline and the hat syntax.

Mauricio Scheffer
Thanks, that seems to be the solution (though it's not as straightforward as I might have hoped). Just to clarify, I would want something like this?let inline sqrt_int (n:^a) = retype (sqrt (float n)) : ^a
Noldorin
Yup, that works. However, be aware that with this you lose compile-time type checking. I.e. sqrt_int "blabla" type-checks although you'll get a FormatException at runtime.
Mauricio Scheffer
Ok, so there's really no point of using the hat operator in this case, right? If I happened to be using an arithmetic operator such as * in the function (before the cast), would that insure the compile-time check?
Noldorin
Yes, in this case a normal type parameter works too: let inline sqrt_int (n:'a) = retype (sqrt (float n)) : 'aNot sure what you mean with the arithmetic operator.
Mauricio Scheffer
I'll edit the answer to make it more explicit
Mauricio Scheffer
+13  A: 

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
Brian
Good clarification - I think I understand what's going on now. Cheers for that. Having read up on things, It seems like F# is still missing a few of the nice features functional languages such as Haskell have. Perhaps these features will be implemented soon now that F# is a 1st class .NET language.
Noldorin
I love the use of bugaboo
JaredPar
not sure if you want this to be a 'living' question or not but since the current f# release now no longer needs the OverloadId attrib (yey!) you may want to adjust the answer...
ShuggyCoUk
Thanks, I'd already updated the code, but forgot to update the prose.
Brian
@Noldorin: "Perhaps these features will be implemented soon now that F# is a 1st class .NET language". I seriously doubt it given the lack of compelling examples.
Jon Harrop
+1  A: 

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 ->
Joel Mueller
That's what I initially thought I might be able to do. Unfortunately it gives a "runtime coercion" error (error FS0008). Together with the retype function provided in a link in mausch's post, it should however work as an alternative to the inline keyword if I understand it properly.
Noldorin
+4  A: 

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
Brian
Interesting. Now I have too many options! One question: why you need the generic specifier <'a> when you specify a type constraint on x. I thought they were equivalent syntaxes.
Noldorin
You can indeed omit the <'a> (try it). Note the potential perf difference; the method-overloading determines which version at compile-time, whereas this version does run-time type-checking. (It might be that 'sqrt' overwhelms these considerations, though, I haven't measured.)
Brian
And of course the other difference is that this version compiles (and throws at runtime) for non-ints, whereas the method overloading version will compile-time error for non-ints.
Brian
Yeah, so your previous method is perhaps a bit quicker. I ought to benchmark the three solutions posted here and then post back perhaps. Thanks again...
Noldorin