Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When should we use FSharpFunc.Adapt?

Tags:

f#

Looking at the source in FSharp.Core and PowerPack, I see that a lot of higher-order functions that accept a function with two or more parameters use FSharpFunc.Adapt. For example:

let mapi f (arr: ResizeArray<_>) =
   let f = FSharpFunc<_,_,_>.Adapt(f)
   let len = length arr
   let res = new ResizeArray<_>(len)
   for i = 0 to len - 1 do
       res.Add(f.Invoke(i, arr.[i]))
   res

The documentation on FSharpFunc.Adapt is fairly thin. Is this a general best practice that we should be using any time we have a higher-order function with a similar signature? Only if the passed-in function is called multiple times? How much of an optimization is it? Should we be using Adapt everywhere we can, or only rarely?

Thanks for your time.

like image 880
Joel Mueller Avatar asked Mar 17 '11 06:03

Joel Mueller


1 Answers

That's quite interesting! I don't have any official information (and I didn't see this documented anywhere), but here are some thoughts on how the Adapt function might work.

Functions like mapi take curried form of a function, which means that the type of the argument is compiled to something like FSharpFunc<int, FSharpFunc<T, R>>. However, many functions are actually compiled directly as functions of two arguments, so the actual value would typically be FSharpFunc<int, T, R> which inherits from FSharpFunc<int, FSharpFunc<T, R>>.

If you call this function (e.g. f 1 "a") the F# compiler generates something like this:

FSharpFunc<int, string>.InvokeFast<a>(f, 1, "a");

If you look at the InvokeFast function using Reflector, you'll see that it tests if the function is compiled as the optimized version (f :? FSharpFunc<int, T, R>). If yes, then it directly calls Invoke(1, "a") and if not then it needs to make two calls Invoke(1).Invoke("a").

This check is done each time you call a function passed as an argument (it is probably faster to do the check and then use the optimized call, because that's more common).

What the Adapt function does is that it converts any function to FSharpFunc<T1, T2, R> (if the function is not optimized, it creates a wrapper for it, but that's not the case most of the time). The calls to the adapted function will be faster, because they don't need to do the dynamic check every time (the check is done only once inside Adapt).

So, the summary is that Adapt could improve the performance if you're calling a function passed as an argument that takes more than 1 argument a large number of times. As with any optimizations, I wouldn't use this blindly, but it is an interesting thing to be aware of when tuning the performance!

(BTW: Thanks for a very interesting question, I didn't know the compiler does this :-))

like image 112
Tomas Petricek Avatar answered Sep 21 '22 16:09

Tomas Petricek