Reputation: 51008
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
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