This is the inconvenient skeleton of F # in the closet.
Try the following:
> let mapPair f (x,y) = (fx, fy) val mapPair : f:('a -> 'b) -> x:'a * y:'a -> 'b * 'b
Totally generic! Obviously the application application and tuples work.
Now try the following:
> let makeList ab = [a;b] val makeList : a:'a -> b:'a -> 'a list
Hmmm, also common. How about this:
> let makeList ab = [a + b] val makeList : a:int -> b:int -> int list
Yeah, as soon as I have (+) , for some reason it becomes int .
Keep playing:
> let inline makeList ab = [a + b] val inline makeList : a: ^a -> b: ^b -> ^c list when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
Hmmm, interesting. It turns out that if I make the inline function, then F # considers it to be common, but it also gives it this strange when clause, and my common parameters have this strange ^ symbol instead of the usual tick.
This strange syntax is called "statically permitted type parameters" (see here for a somewhat consistent explanation), and the main idea is that the function (+) requires its arguments to have static member (+) . Let it be checked:
> let x = 0 :> obj let y = 0 :> obj let z = x + y Script1.fsx(14,13): error FS0001: The type 'obj' does not support the operator '+' > type My() = static member (+)( a:My, b:My ) = My() let x = My() let y = My() let z = x + y val x : My val y : My val z : My
Now the problem is that the CLR does not support such common parameters (that is, "any type if it has such and such members"), so F # must fake it and allow these calls at compile time. But because of this, any methods that use this function cannot be compiled for true general IL methods and, therefore, must be monomorphic (which is included inline ).
But then it would be very inconvenient to require that every function using arithmetic operators be declared inline, wouldn't it? Thus, F # performs another additional step and tries to fix these statically permitted common parameters based on how they are created later in the code. Therefore, your function turns into string->string->string as soon as you use it with string once.
But if you mark your inline function, F # will not need to fix the parameters, because it will not need to compile the function before IL, and therefore your parameters remain unchanged:
> let inline add ab = a + b val inline add : a: ^a -> b: ^b -> ^c when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)