この記事は、Stack Overflow のすばらしいスレッド、When should we use FSharpFunc.Adapt?を和訳したものです。和訳文章のライセンスは、Stack Overflow のそれと同じく Creative Commonsに従います。
Question
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:
FSharp.Core や PowerPack のソースを見ると、“2つ以上の引数を持つ関数”を引数に取るような高階関数の多くで、FSharpFunc.Adaptが使われています。以下に一例を挙げます。
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?
FSharpFunc.Adapt のドキュメントは薄っぺらすぎます。これって、同じようなシグネチャの高階関数を持つときはいつでも使うべき、一般的なベスト プラクティスなんでしょうか? それか、渡された関数が複数回呼び出される場合だけ? どのくらいの最適化なんだろう? 私たちが Adapt を使用するべきときとは、できるならどこでもなのか、あるいはごくまれになのか?
Thanks for your time.
お時間を取っていただいてありがとう。
asked Mar 17 '11 at 6:58
Joel Mueller
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.
実に面白い! 私はまったく公式情報を持っていませんが(というか、どこにも文書化されてるのを見たことありませんが)、Adapt 関数がどのように働くのかちょっと考察してみます。
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>>.
mapi みたいな関数は、カリー化形式の関数を受け取りますが、これは引数の型が FSharpFunc<int, FSharpFunc<T, R>> のようにコンパイルされる、ということを意味します。しかしながら、多くの関数が実際には2引数の関数に直接コンパイルされる、つまり実際の値は FSharpFunc<int, FSharpFunc<T, R>> を継承した FSharpFunc<int, T, R> になる、というのが普通です。
If you call this function (e.g. f 1 "a") the F# compiler generates something like this:
この関数を呼び出すならば(例:f 1 "a")、F# コンパイラはこんなものを生成します。
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").
Reflector を使って InvokeFast 関数を見てみると、(f :? FSharpFunc<int, T, R>) という風に、渡された関数が最適化されたバージョンとしてコンパイルされているかどうかテストしていることがわかります。yes の場合は Invoke(1, "a") を直接呼び出して、そうでない場合は Invoke(1).Invoke("a") という2つの呼び出しを行う必要があります。
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).
このチェックは、関数に実引数を1つ渡して呼び出すたびに行われます(チェックを行ってから最適化された呼び出しを使用する方がたぶん速いです、その方がより一般的ですから)。
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).
Adapt 関数が行うこととは、任意の関数を FSharpFunc<T1, T2, R> に変換することです(関数が最適化されていない場合、そのためのラッパーを作成しますが、そんなケースはほとんどありません)。adapted な関数は、動的チェックを毎回行う必要がないために、呼び出しがより速くなります(チェックは 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 optimizaions, I wouldn't use this blindly, but it is an interesting thing to be aware of when tuning the performance!
で、要するに Adapt は、2つ以上の引数を取る関数に対して引数を渡して呼び出してを何度も繰り返し行う場合に、パフォーマンスを向上させることができます。 任意の最適化と同様に、盲目的に使うようなものではないだろうけど、パフォーマンス チューニングするときには注意が必要な興味深い点ですね!
(BTW: Thanks for a very interesting question, I didn't know the compiler does this :-))
(追伸:とっても興味深い質問ありがとう、コンパイラがこんなことしてるなんて知らなかったよ :-))
answered Mar 17 '11 at 12:33
Tomas Petricek