Larry Lustig
Larry Lustig

Reputation: 51008

Cannot invoke partially applied F# function as a method from C#

I wrote a simple function in F# and was able to call it correctly from C# code in another project in the same solution. I then attempted to refactor so that the F# function I was calling from C# was the result of partial application of the original function and receive the compile-time error Non-invocable member: 'Calculators.TenPercentCalculator' cannot be used like a method.

Here is the working F#:

    let TenPercentItemCalculator (paymentItem : PaymentItem) = 
        1.1M * paymentItem.Amount

    let ExtractItems (payment : Payment) = List.ofSeq(payment.PaymentItems) 

    let TenPercentCalculator  (payments : Payment seq) =
        payments |> Seq.map ExtractItems |> List.concat |> List.map TenPercentItemCalculator |> List.sum

and here is the refactored F# which does not compile:

    let TenPercentItemCalculator (paymentItem : PaymentItem) = 
        1.1M * paymentItem.Amount

    let ExtractItems (payment : Payment) = List.ofSeq(payment.PaymentItems) 

    let BaseCalculator (itemCalculator: PaymentItem -> decimal)  (payments : Payment seq) =
        payments |> Seq.map ExtractItems |> List.concat |> List.map itemCalculator |> List.sum

    let TenPercentCalculator : (Payment seq -> decimal) = BaseCalculator TenPercentItemCalculator

and in both cases, here's the C# code that calls the TenPercentCalculator function:

var interest = Calculators.TenPercentAuctionCalculator(SampleLedger.Payments);

Upvotes: 4

Views: 245

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243096

In principle, removing the parameter and using partial function application gives you the same function, but there are subtle differences. The obvious one is value restriction (but that does not apply here). The more subtle one is how the function is compiled.

You can actually see this, even in the F# type signature. Consider:

let normal nums = Array.map ((+) 1) nums
let partial = Array.map ((+) 1)

The types of the two are:

val normal : int [] -> int []
val partial : (int [] -> int [])

As you can see, the compiler prints additional parentheses when the function is partially applied. Most of the time, you can ignore this, but it means that the function will be compiled as a property returning FSharpFunc<...> rather than as an ordinary .NET method.

You can still call it (if you also add reference to FSharp.Core):

Foo.normal(Enumerable.Range(1, 10).ToArray());
Foo.partial.Invoke(Enumerable.Range(1, 10).ToArray());

But if you are designing library that should be easy to use from C#, then I would simply avoid using partial function application in the public API (as well as other F#-specific types).

Upvotes: 6

Related Questions