Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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?!?

like image 374
Bruno Reis Avatar asked Jan 08 '10 06:01

Bruno Reis


People also ask

Facebook itu apa sih?

Facebook adalah media sosial dan layanan jejaring sosial online Amerika yang dimiliki oleh Meta Platforms.


2 Answers

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, o.M(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 e.g. have two overloads, Add(int) and Add(Tuple<int,int>), then a call of the form Add(1,2) wouldn't resolve at all.

like image 143
Pavel Minaev Avatar answered Oct 01 '22 11:10

Pavel Minaev


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.

like image 32
Blindy Avatar answered Oct 01 '22 12:10

Blindy