Can someone explain this F# curiosity?

    type IFoo =
        abstract member Bar1: int * int -> int * int
        abstract member Bar2: int * int -> (int * int)
        abstract member Bar3: (int * int) -> int * int

    type Foo() = class end
        interface IFoo with
            member this.Bar1 (x, y) = (x, y)
            member this.Bar2 (x, y) = (x, y) // Same impl as Bar1 i.e. parentheses on RHS of -> in interface member definition are ignored
            // member this.Bar3 (x, y) = (x, y) // Compile error: "This override takes a different number of arguments to the corresponding abstract member"
            member this.Bar3 tuple = tuple // So, parentheses on LHS of -> in interface member definition *does* make a difference!

What is the difference in meaning between the definitions of IFoo.Bar1 and IFoo.Bar3?


Here, the input type can describe two different things: a tuple or the argument list of a CLI method.

This makes no difference on the return type, since the only interpretation of this return type is a tuple. But on the argument list, you can decide between a CLI method that takes two arguments, or a CLI method that takes one argument, which happens to be a tuple. The latter is denoted by the extra parentheses.

That is why you can implement Bar3 with a single argument, typed as a tuple, which isn't possible with the others (as of F# 3).

This is also a place where double parentheses make a difference to single parentheses. If Bar3 weren't abstract, you could enforce tuple input by declaring it as member this.Bar3 ((arg1, arg2)).

Method argument lists have additional features, such as optional arguments. So:

type Test () =
    member t.BarA(a, ?b) = a
    member t.BarT((a, ?b)) = a // Error

The last line gives the error "Optional arguments are only allowed on type members", since b is now part of a tuple pattern, not an argument in the method's argument list.

  • Interesting - CLI details are leaking into the F# syntax. I'm guessing this is necessary for interop reasons? For pure F# code, I can't see any reason to distinguish the two cases. – Akash Nov 27 '14 at 14:54
  • 1
    @Akash No matter why, the world of CLI methods has become a part of F# as a multi-paradigm language. It has overloading, inheritance and overriding, optional arguments and so forth, and these features aren't considered exclusive to interop in the way null is. But they don't always combine well with functional-first programming. In pure functional code, these features are not used, so the functional part maps well to CLI methods -- except for the rare awkwardness shown in your question. – Vandroiy Nov 27 '14 at 16:12

Your Answer

