F#:コンパイル後にコードはどうなりますか

F#言語はごく最近、標準のVisualStudioパッケージに登場しました。つまり、2010バージョン(現在、最も関連性の高いバージョン)からです。 当然、誰もがこのことをよく知っています。言語はCLRに基づいて機能します。すべてのコードは、他の.NET言語と同様にMS ILにコンパイルされます。



よく使用される便利な暗記手法の例として、コンパイラーがコードを変換するものを見てみましょう。 明確にするために、F#自体でコードを記述し、C#で逆コンパイルします。



したがって、元の例:

let memoize f = let cache = System.Collections.Generic.Dictionary() fun x -> if cache.ContainsKey(x) then cache.[x] else let res = fx cache.[x] <- res res let rec memoizedFactorial = let factorial x = match x with | a when a = 0I -> 1I | x -> x * memoizedFactorial (x - 1I) memoize factorial let now = System.DateTime.Now let arguments = [10000I .. 10100I] let factorials = arguments |> List.map memoizedFactorial let timeDiff = System.DateTime.Now - now printfn "Time: %A" timeDiff
      
      







いくつかの説明:





さて、記念が何につながったのか見てみましょう。

記憶なしのプログラム実行時間は28.97秒で、記憶ありでは0.89秒です。 ことわざにあるように、効果は明ら​​かです。



これがすべてコンパイルするものを調べます。 C#プログラマーにとって最も興味深い点は、もちろんmemoizedFactorial



関数を呼び出す瞬間です。 ローカル関数キャッシュが再作成されないのはなぜですか?

見てみましょう。



main



機能:

 public static void main@() { memoizedFactorial@11-1 = LazyExtensions.Create<FSharpFunc<BigInteger, BigInteger>>(new Program.clo@11-1()); memoizedFactorial@11 = Program.memoizedFactorial@11.get_Value(); now@18 = DateTime.Now; arguments@20 = SeqModule.ToList<BigInteger>(new Program.arguments@20(0, new BigInteger())); factorials@21 = ListModule.Map<BigInteger, BigInteger>(Program.memoizedFactorial, Program.arguments); timeDiff@23 = (TimeSpan) (DateTime.Now - Program.now); fp@1 = new PrintfFormat<FSharpFunc<TimeSpan, Unit>, TextWriter, Unit, Unit, TimeSpan>("Time: %A"); PrintfModule.PrintFormatLineToTextWriter<FSharpFunc<TimeSpan, Unit>>(Console.Out, Program.fp@1).Invoke(Program.timeDiff); }
      
      







うーん... memoizedFactorial



関数memoizedFactorial



別のオブジェクトになりました。 さらに、変数はまったくありません。これらはすべて、クラスinternal static class $Program



フィールドになりました。 一般に、特定のクラスまで、コードは明確でなければなりません。 memoizedFactorial



入ります。



まず、 $Program



クラスの2つのフィールドがあります。

 internal static FSharpFunc<BigInteger, BigInteger> memoizedFactorial@11; internal static Lazy<FSharpFunc<BigInteger, BigInteger>> memoizedFactorial@11-1;
      
      







2番目は、クロージャーが渡される標準クラスのオブジェクトによって初期化されることがわかります。 最初のフィールドは興味深いことを何もせず、それ自体を返します( get_Value



メソッド)。



したがって、クロージャは次のようになります。

 internal class clo@11-1 : FSharpFunc<Unit, FSharpFunc<BigInteger, BigInteger>> { // Methods internal clo@11-1(); public override FSharpFunc<BigInteger, BigInteger> Invoke(Unit arg00@); }
      
      







これは興味深いメソッドを持つクラスであることを理解しています。

 public override FSharpFunc<BigInteger, BigInteger> Invoke(Unit arg00@) { return Program.memoizedFactorial@11-1(); }
      
      







うん 彼らはすでにいた場所に来たようです。 しかし、これはそうではありません。 これは、 memoizedFactorial@11-1



関数を呼び出した結果を返します。 最後に、この関数を見てみましょう:

 internal static FSharpFunc<BigInteger, BigInteger> memoizedFactorial@11-1() { return memoize<BigInteger, BigInteger>(new clo@13()); }
      
      







こっち! 最後に、 memoize



関数にmemoize



。 そして別の閉鎖へ。 クロージャは同じクラスの継承クラスなので、すぐにInvokeメソッドを見てみましょう。

 public override BigInteger Invoke(BigInteger x) { if (LanguagePrimitives.HashCompare.GenericEqualityIntrinsic<BigInteger>(x, BigInteger.Zero)) { return BigInteger.One; } return (x * Program.memoizedFactorial@11.get_Value().Invoke(x - BigInteger.One)); }
      
      







ここに階乗です! 値を直接計算する関数を見つけました。 F#コードで書いたように、メモリ化されたバージョンが再帰に使用されることに注意してください。 テンプレートとの比較は、通常のif



に変換されました。 また、途中で、2つのBigInteger



ハッシュを通じて比較されることに気付きました。



一歩戻って、 memoize



関数を見てみましょう。

 public static FSharpFunc<a, b> memoize<a, b>(FSharpFunc<a, b> f) { return new memoize@3<a, b>(f, new Dictionary<a, b>()); }
      
      







ここから、楽しみが始まりました。 作業の結果として、オブジェクトは、クロージャー(最初の引数)と辞書(2番目の引数)が渡されるコンストラクターパラメーターに返されます。 これで、辞書が毎回再作成されない理由が明らかになりました。



私たちは深く行きます:

 internal class memoize@3<a, b> : FSharpFunc<a, b> { // Fields public Dictionary<a, b> cache; public FSharpFunc<a, b> f; // Methods internal memoize@3(FSharpFunc<a, b> f, Dictionary<a, b> cache); public override b Invoke(ax); }
      
      







クラスフィールドは、キャッシュがプログラム全体のキャッシュである理由を再度示しています。 Invoke



関数を見てみましょう。

 public override b Invoke(ax) { if (this.cache.ContainsKey(x)) { return this.cache[x]; } b res = this.f.Invoke(x); this.cache[x] = res; return res; }
      
      







そして、ここに私たちの記念機能があります! キーは引数を介して渡されます。 オブジェクトのフィールドを介してすでに知っているクロージャーf



。 その秘密は解明されています。



したがって、F#には魔法はありません。 まあ、絶対に。 これらのすべてのカリーmemoizedFactorial



(およびmemoizedFactorial



関数memoizedFactorial



そのままです)、ファーストクラスの市民のような関数は、実際には単に自動生成されたクラスとオブジェクトです。 私の記事が、誰かがF#がどのように機能するかをよりよく理解するのに役立つことを願っています。



All Articles