15

I just learned that OCAML have to have a . postfix for doing float arithmetic. An example would be 3. +. 4. which equals 7. (float). However, F# handles float and integer arithmetic in the same way, so both 3 + 4 (int) and 3. + 4. (float) works.

F# have + naturally assigned to int so let add a b = a + b is of type int -> int -> int. And indeed (+) gives me val it : (int -> int -> int) = <fun:it@6-1>.

That leads to the following sequence which I think quite counter-intuitive:

> 3. + 4.;;
val it : float = 7.0
> (+);;
val it : (int -> int -> int) = <fun:it@8-2>

So my question is: Is the "overloading" done by a special mechanism/case in the compiler or is this a language-wide thing so I potentially can define a function called add (or anything else) which have a one definition for integers and one for floats (or any other type.)

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
Lasse Espeholt
  • 17,622
  • 5
  • 63
  • 99
  • 1
    You may be interested in this: http://stackoverflow.com/questions/4732672/how-to-write-a-function-for-generic-numbers/4738404#4738404 – Daniel Jun 08 '11 at 21:34
  • 1
    @Daniel Compile-time type constrains. Nice, thanks :) However, the question still stands: is this how it is done in F#? – Lasse Espeholt Jun 08 '11 at 21:39
  • Not sure I follow. Generic functions can only be written `inline`, if that's what you're asking. – Daniel Jun 08 '11 at 21:41

3 Answers3

13

Briefly, F# has an ad-hoc-overloading mechanism via the inline keyword and "static member constraints". There is some further magic specific to the built-in math operators, which magically assumes type int the absence of other constraints. (+) is just about the most special/magical thing in all of F#, so it does not make for a nice introduction to the language/type system.

In general, "overloading" is difficult for statically-typed, type-inferred languages. F#'s choices here are very pragmatic. OCaml does a different, simple, pragmatic thing (no overloading). Haskell does a different, complex-but-elegant thing (type classes). They're all somewhat reasonable points in the language/library design space.

Brian
  • 117,631
  • 17
  • 236
  • 300
7

Overloaded functions (and operators) must be marked inline in F#. This is because they depend on explicit member constraints. Those constraints are resolved at compile time. A function let inline add a b = a + b has the type 'a -> 'b -> 'c (requires member (+)) where + is a static function/operator. You can't do this in C#; it doesn't have static member constraints.

let inline add a b = a + b
add 1 2 //works
add 1.0 2.0 //also works
Daniel
  • 47,404
  • 11
  • 101
  • 179
  • 1
    +1 Cool :) I see from the type of the function that it uses compile type constraints. However, this doesn't answer the question entirely. What is the definition of `(+)` really when it works on both? – Lasse Espeholt Jun 08 '11 at 21:42
  • Something like: `'a -> 'b -> 'c (requires member (+))` That means, it works for any type having a static member `(+)`. – Daniel Jun 08 '11 at 21:43
  • What is `(+)`? :) It tells me it is `int -> int -> int` but it works on float, why? I'm sorry if it isn't clear what I mean :/ – Lasse Espeholt Jun 08 '11 at 21:51
  • You can see the code here: https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/prim-types.fs It is gory and you don't want to know the details. Some of it is specific compiler magic for the built-in math operators. More generally, you can use `inline` and static member constraints to do similar duck-typing stuff in user code, but it is also gory and you do not want to know the details. The short answer is: "magic". – Brian Jun 08 '11 at 21:53
  • @lass: It has the type `int -> int -> int` unless you mark it `inline`. So, yes, F# seems to pick `int` by default, unless it can infer a better type based on usage (i.e., if your initial call to the function uses float parameters, for example). – Daniel Jun 08 '11 at 21:57
  • @lass: I think you may be confusing the signature of `+` with that of your function using `+`. `+` is generic, but a function using it is not, unless marked `inline`. This is because use of the operator depends on the presence of a static member. (Sorry if I'm sounding repetitive.) – Daniel Jun 08 '11 at 22:14
  • @Daniel Yeah, I think you're right. F# is a lot more complex than I thought. I appreciate your helpfulness :-) – Lasse Espeholt Jun 08 '11 at 22:19
  • @lass: Yes, but I find it's the type of complexity that makes things simpler. However, it can drive you crazy trying to figure out how some of it works. – Daniel Jun 08 '11 at 22:22
4

In addition to Brian´s answer and link:

https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/prim-types.fs

I found some definitions in the code:

let inline (+) (x:int) (y:int) = (# "add" x y : int #)

and

let inline (+) (x: ^T) (y: ^U) : ^V = 
     AdditionDynamic<(^T),(^U),(^V)>  x y 
     when ^T : int32       and ^U : int32      = (# "add" x y : int32 #)
     when ^T : float       and ^U : float      = (# "add" x y : float #)
     when ^T : float32     and ^U : float32    = (# "add" x y : float32 #)
     ...

And the AdditionDynamic is defined here (loads of static stuff and CIL): https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/prim-types.fs#L2374

Fun stuff:

(# "add" 1 2 : int32 #)

works, and gives 3 as output (with a warning saying you shouldn't do this.)

Lasse Espeholt
  • 17,622
  • 5
  • 63
  • 99
  • Yes the FSharp.Core library has some magic to access underlying IL constructs when there is no .NET API to do it. You should not use the `(# ... #)` stuff in your own code, it is a bug that the compiler does not reject this outright (and future versions of the compiler will reject it outright). – Brian Jun 08 '11 at 22:19
  • 5
    @Brian: Why shouldn't we be able to write inline IL? It's not a bug; it's a feature. :-) – Daniel Jun 08 '11 at 22:24