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
with
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With