3
votes

From f# I'm trying to call a function on a C# defined object using a member constraint. Since the c# member function take multiple arguments the f# compiler treats it as a tuple but when applying the generic member constraint I get an error that the function takes 4 arguments but I've only supplied one.

I've tried forming the tuple from the arguments or just taking the pre-tupled set of arguments but both give the same error. I think I must be defining my member constraint incorrectly but there aren't a lot of examples of member constraints with multiple arguments.

let inline wrapForDecode (buffer:DirectBuffer) (offset:int) (blockLen:uint16) (version:uint16) : ^a =
  let msg = new ^a()
  (^a : (member WrapForDecode : (DirectBuffer*int*int*int)->unit) msg, (buffer, offset, (int blockLen), (int version)))
  msg

let inline wrapForDecode2 (args :DirectBuffer*int*int*int) : ^a =
  let msg = new ^a()
  (^a : (member WrapForDecode : (DirectBuffer*int*int*int)->unit) (msg, args))
  msg

The original WrapForDecode member function is defined in c# like:

public void WrapForDecode(DirectBuffer buffer, int offset, int actingBlockLength, int actingVersion) {...}

When I try to call the function I get the following error for either wrapForDecode or wrapForDecode2.

The member or object constructor 'WrapForDecode' takes 4 argument(s) but is here given 1. The required signature is 'MDInstrumentDefinitionFuture27.WrapForDecode(buffer: DirectBuffer, offset: int, actingBlockLength: int, actingVersion: int) : unit'.
1

1 Answers

7
votes

If you change the type of WrapForDecode's argument from (DirectBuffer*int*int*int) to DirectBuffer*int*int*int the first inline method will compile:

let inline wrapForDecode (buffer:string) 
                         (offset:int)
                         (blockLen:uint16)
                         (version:uint16)
                         : ^a =
  let msg = new ^a()
  (^a : (member WrapForDecode : string * int * int * int -> unit) 
      msg, 
      buffer, 
      offset, 
      (int blockLen), 
      (int version))
  msg

type foo () = 
    member this.WrapForDecode (a : string, b: int, c: int, d:int) = 
        ()

let x : foo = wrapForDecode "asd" 1 2us 3us

In ordinary F# code, the two signatures would be equivalent - all methods take a single argument, and to write a function with arity > 1 it must either be curried or take a tupled argument.

However, that is not how the CLI works - in C#/VB.Net land, foo1(x : bar, y : baz) has a different signature from foo2(xy : Tuple<bar, baz>).

Normally the F# compiler automatically translates between the two styles, and so when accessing non-F# .NET code from F# you will see both methods as taking a tupled argument.

But statically-resolved member constraints are a complicated and relatively fringe feature of F#, so it appears that this automatic translation isn't or cannot be performed when invoking methods this way.

(thanks to @ildjarn for pointing out the source of this difference)