简体   繁体   中英

F# - On the parameters passed to C# methods - are they tuples or what?

I've read many times that

Assemblies generated from F# or any other .NET language are (almost) indistinguishable.

I was then experimenting with F# and C# interop on .NET 4 (beta 2). I created a new solution, and a C# project, with the following class:

public class MyClass {
    public static int Add(int a, int b) { return a + b; }
}

Then, on a F# project, after referencing the C# project, I tried:

MyClsas.Add(4, 5) |> printfn "%d" // prints 9 (no kidding!)

So far so good. Then another sentence I've read many times (perhaps on different books) came to my mind:

When passing arguments to functions from other .NET libraries, you use a syntax like ".MethodName(parm1, parm2)", that is, the parameters are passed as a Tuple.

Add that to something that I've once read here on SO (but wasn't able find it to link to), on a question where the OP was trying to create a using like [ 4, 5, 6 ] (when he meant [4; 5; 6] ):

"Comma is the 'tuple creating operator', for everything else use semi-colon."

Then I modified my class to the following:

public class MyClass {
    public static int Add(int a, int b) { return a + b; }
    public static int Add(Tuple<int, int> a) { return a.Item1; }
}

Now I tried to use it on F#:

MyClass.Add(4, 5) |> printf "%d" // prints ... (keep reading!)

So, adding up the three quotations above, one can conclude that:

  • F# will create a Tuple when it sees (4, 5)
  • Then it will call the overload Add(Tuple<int, int>)
  • So it will print 4

To my surprise, it printed 9 . Isn't it interesting?

What is really happening here? The above quotations and this practical observations seems to be in contradiction. Can you justify F#'s "reasoning", and maybe pointing to some MSDN docs if possible?

Thanks!

EDIT

(to add more information (from Blindy's answer))

If you do:

MyClass.Add((4, 5)) |> printfn "%d" // prints 9

F# calls the Add(Tuple<int, int>) overload.

However, if you create another F# project (so a different assembly) with this:

namespace MyFSharpNamespace
type MyFShapClass = class
    static member Add x y = x + y
    end

You can use it on C# like this

public static void Main(string[] args) {
    MyFSharpNamespace.MyFSharpClass.Add(4, 5);
}

So far so good. Now, when you try to use it from F# (from another project, another assembly), you have to do:

MyFSharpNamespace.MyFSharpClass.Add 4 5 |> printfn "%d"

If you pass the arguments as (4, 5) F# will not compile because Add is int -> int -> int , and not (int * int) -> int .

What is happening?!?

When passing arguments to functions from other .NET libraries, you use a syntax like ".MethodName(parm1, parm2)", that is, the parameters are passed as a Tuple.

It's more hideous than that. See the description of method overload resolution strait from the language spec .

What it says, basically, is that argument in a method invocation isn't really a tuple. It's a syntactic tuple, meaning a comma-separated list of something, but the parentheses are part of the method call syntax, and so are the commas. It's why, for example, oM(a=1, b=2) isn't a method call with a tuple of two booleans, but rather two named arguments.

So, normally, every comma-separated component just maps to a distinct argument. Hence why Add(1, 2) calls Add(int, int) overload, and Add((1, 2)) calls Add(Tuple<int, int>) . There is no ambiguity here.

However, a special case that kicks in for your particular case is this:

If there are no named actual arguments, and there is only one candidate method in M , accepting only one non-optional argument, then the decomposition of arg to tuple form is ignored and there is one named actual arg which is arg itself.

So when you removed all overloads except for the tuple one, suddenly the entire thing inside the parentheses is effectively treated as a tuple constructor in a call. But if you'd eg have two overloads, Add(int) and Add(Tuple<int,int>) , then a call of the form Add(1,2) wouldn't resolve at all.

I don't have F# installed right now, but it seems to me that

MyClass.Add(4, 5) |> printf "%d"

would print 9 whereas

MyClass.Add((4, 5)) |> printf "%d"

would print.. 4 right? Notice the double parantheses, the inner pair marking a tuple and the outer pair marking the function call.

It's just compiler magic.

let add a b = a+b 

add compiles down to add(a,b) , making it easy to call from C#. However, F# programs still see it as add ab due to an attribute in the IL.

When calling C# functions in F#, it may help to think of the C# function as having only one parameter -- a tuple, whose elements determine the correct overload. So you can write:

// MyClass.Add(5,3) = 8
let eight = (5,3) |> MyClass.Add

I'm not an F# expert, so I may be a bit off base, but I'd guess that the F# concept of a tuple doesn't correlate with the BCL System.Tuple type. Tuples are a core tenet of F# and are built into the language, but C#, VB.NET, and most other .NET languages do not natively support tuples. Since tuples can be useful in these languages, the library is gaining support for them.

I would further my supposition to say that an F# tuple is represented in memory in very much the same way that parameters are passed to methods in C# and friends. That is, they are essentially value arrays of their components. When this value array is pushed onto the stack for a method call, it would have the same effect as pushing each of its constituent components onto the stack, just like when that method is called from C#.

So your second example creates an F# tuple, pushes it onto the stack, then calls the Add overload that takes the types contained within the tuple.

That's my guess, anyway. Having presumably used F# more than I, you might have more insight beyond this. You might also gain additional clues by looking at the generated code Reflector .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM