カリー化 続々
クロージャを実装できた
関数外の変数に手を出している関数をマークして
関数外の変数を一個のframeにまとめる
var a = 1 var b = 2 sub f(c: Int) print(a + b + c) f(3)
↑これを↓こう変換する
var a = 1 var b = 2 sub f_(frame, c: Int) print(frame.a + frame.b + c) var frame # この辺が変換部分 frame.a = a frame.b = b f = f_.bind(frame) f(3)
CILに変換するときは前回の部分適用でなんとかする
今のつくりはクロージャがネストすると1関数1frameで作っているので、どんどん引数が増える
まぁいっか
ILは笑っちゃうほど長い
// Microsoft (R) .NET Framework IL Disassembler. Version 4.6.81.0 // Copyright (c) Microsoft Corporation. All rights reserved. // Metadata version: v4.0.30319 .assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 4:0:0:0 } .assembly Global { .hash algorithm 0x00008004 .ver 0:0:0:0 } .module Global // MVID: {3B864071-7E41-437A-9EDF-B63D9808492F} .imagebase 0x00400000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000001 // ILONLY // Image base: 0x00CA0000 // ================== GLOBAL METHODS ========================= .method public static class [mscorlib]System.Action`1<int32> f1(int32 A_0) cil managed { // コード サイズ 38 (0x26) .maxstack 2 .locals init (class '##f1' V_0, class [mscorlib]System.Action`1<int32> V_1) IL_0000: newobj instance void '##f1'::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: ldarg.0 IL_0008: stfld int32 '##f1'::w IL_000d: ldloc.0 IL_000e: ldc.i4.5 IL_000f: stfld int32 '##f1'::z IL_0014: ldloc.0 IL_0015: ldc.i4 0x14 IL_001a: nop IL_001b: nop IL_001c: nop IL_001d: nop IL_001e: call class [mscorlib]System.Action`1<int32> f2(class '##f1', int32) IL_0023: stloc.1 IL_0024: ldloc.1 IL_0025: ret } // end of global method f1 .method public static class [mscorlib]System.Action`1<int32> f2(class '##f1' A_0, int32 A_1) cil managed { // コード サイズ 70 (0x46) .maxstack 3 .locals init (class '##f2' V_0, class [mscorlib]System.Action`1<int32> V_1) IL_0000: newobj instance void '##f2'::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: ldarg.1 IL_0008: stfld int32 '##f2'::x IL_000d: ldloc.0 IL_000e: ldloc.0 IL_000f: ldfld int32 '##f2'::x IL_0014: stfld int32 '##f2'::y IL_0019: newobj instance void Bind_f3::.ctor() IL_001e: stloc.1 IL_001f: ldloc.1 IL_0020: ldftn void f3(class '##f1', class '##f2', int32) IL_0026: stfld native int Bind_f3::f IL_002b: ldloc.1 IL_002c: ldarg.0 IL_002d: stfld class '##f1' Bind_f3::'##f1' IL_0032: ldloc.1 IL_0033: ldloc.0 IL_0034: stfld class '##f2' Bind_f3::'##f2' IL_0039: ldloc.1 IL_003a: ldftn instance void Bind_f3::Invoke(int32) IL_0040: newobj instance void class [mscorlib]System.Action`1<int32>::.ctor(object, native int) IL_0045: ret } // end of global method f2 .method public static void f3(class '##f1' A_0, class '##f2' A_1, int32 A_2) cil managed { // コード サイズ 65 (0x41) .maxstack 2 .locals init (int32 V_0, int32 V_1, int32 V_2, int32 V_3, int32 V_4) IL_0000: ldc.i4 0xa IL_0005: nop IL_0006: nop IL_0007: nop IL_0008: nop IL_0009: ldarg.1 IL_000a: ldfld int32 '##f2'::x IL_000f: add IL_0010: stloc.0 IL_0011: ldloc.0 IL_0012: ldarg.1 IL_0013: ldfld int32 '##f2'::y IL_0018: add IL_0019: stloc.1 IL_001a: ldloc.1 IL_001b: ldarg.0 IL_001c: ldfld int32 '##f1'::z IL_0021: add IL_0022: stloc.2 IL_0023: ldloc.2 IL_0024: ldarg.0 IL_0025: ldfld int32 '##f1'::w IL_002a: add IL_002b: stloc.3 IL_002c: ldloc.3 IL_002d: ldarg.2 IL_002e: add IL_002f: stloc V_4 IL_0033: nop IL_0034: nop IL_0035: ldloc V_4 IL_0039: nop IL_003a: nop IL_003b: call void [mscorlib]System.Console::WriteLine(int32) IL_0040: ret } // end of global method f3 .method public static void '###.ctor'() cil managed { // コード サイズ 15 (0xf) .maxstack 2 .locals init (class [mscorlib]System.Action`1<int32> V_0) IL_0000: ldc.i4.1 IL_0001: call class [mscorlib]System.Action`1<int32> f1(int32) IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldc.i4.2 IL_0009: callvirt instance void class [mscorlib]System.Action`1<int32>::Invoke(!0) IL_000e: ret } // end of global method '###.ctor' .method family static void __EntryPoint() cil managed { .entrypoint // コード サイズ 6 (0x6) .maxstack 0 IL_0000: call void '###.ctor'() IL_0005: ret } // end of global method __EntryPoint // ============================================================= // =============== CLASS MEMBERS DECLARATION =================== .class private auto ansi '##f1' extends [mscorlib]System.Object { .field public int32 w .field public int32 z .method public specialname rtspecialname instance void .ctor() cil managed { // コード サイズ 1 (0x1) .maxstack 0 IL_0000: ret } // end of method '##f1'::.ctor } // end of class '##f1' .class private auto ansi '##f2' extends [mscorlib]System.Object { .field public int32 x .field public int32 y .method public specialname rtspecialname instance void .ctor() cil managed { // コード サイズ 1 (0x1) .maxstack 0 IL_0000: ret } // end of method '##f2'::.ctor } // end of class '##f2' .class private auto ansi Bind_f3 extends [mscorlib]System.Object { .field public native int f .field public class '##f1' '##f1' .field public class '##f2' '##f2' .method public specialname rtspecialname instance void .ctor() cil managed { // コード サイズ 7 (0x7) .maxstack 2 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method Bind_f3::.ctor .method public instance void Invoke(int32 A_1) cil managed { // コード サイズ 30 (0x1e) .maxstack 4 IL_0000: ldarg.0 IL_0001: ldfld class '##f1' Bind_f3::'##f1' IL_0006: ldarg.0 IL_0007: ldfld class '##f2' Bind_f3::'##f2' IL_000c: ldarg A_1 IL_0010: nop IL_0011: nop IL_0012: ldarg.0 IL_0013: ldfld native int Bind_f3::f IL_0018: calli void(class '##f1',class '##f2',int32) IL_001d: ret } // end of method Bind_f3::Invoke } // end of class Bind_f3 // ============================================================= // *********** 逆アセンブルが完了しました ***********************
ところで得正のカレーうどんはうまかった
関数のカリー化
CILでカリー化をする時のメモ
部分適用かな?まぁどっちでもいいや
クロージャをCILで実装するのに部分適用でやりたかったらメモ
例えばこんな感じの3引数の関数で先頭の引数をカリー化しちゃう
curryなんて関数があった場合
int32 f1(int32 a, int32 b, int32 c) { return a + b + c; } bind = curry(f1, 1) Console.WriteLine(bind(2, 3)) // => 6
引数の数に応じてカリー化を行うクラスを作っておく
関数と1個めの引数は呼び出し側でセットしてもらう
class Curry<T1, T2, T3, R> { public native int f; public T1 var; int32 lambda(T2 x2, T3 x3) { calli self.f(self.var, x2, x3) } }
呼び出し側はCurryのフィールドにセットしつつ
Funcに引き渡してあとはよしなにしてもらう
CILへの変換時に「bind = curry(f1, 1)」をこんな感じのコードにして
bindって引数にこれFuncでっせというフラグを付けておく
そのフラグ見てcallvirtする感じ
.assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) .ver 4:0:0:0 } .assembly Global { .hash algorithm 0x00008004 .ver 0:0:0:0 } .method public static int32 f1(int32 a, int32 b, int32 c) { // return a + b + c ldarg.0 ldarg.1 add ldarg.2 add ret } .class public Curry<T1, T2, T3, R> { .field public native int f .field public !T1 var .method instance void .ctor() { ret } .method instance !R lambda(!T2 x2, !T3 x3) { // self.f(self.var, x2, x3) ldarg.0 ldfld !T1 class Curry<!T1, !T2, !T3, !R>::var ldarg.1 ldarg.2 ldarg.0 ldfld native int class Curry<!T1, !T2, !T3, !R>::f calli !R (!T1, !T2, !T3) ret } } .method public static void __EntryPoint() cil managed { .entrypoint .locals ( class Curry<int32, int32, int32, int32> curry, class [mscorlib]System.Func`3<int32, int32, int32> bind ) // Console.WriteLine(f1(1, 2, 3)) ldc.i4.1 ldc.i4.2 ldc.i4.3 call int32 f1(int32, int32, int32) call void [mscorlib]System.Console::WriteLine(int32) // bind = curry(f1, 1) // Console.WriteLine(bind(2, 3)) // curry = new Curry<int32, int32, int32, int32> newobj instance void class Curry<int32, int32, int32, int32>::.ctor() stloc.0 // curry.f = f1 ldloc.0 ldftn int32 f1(int32, int32, int32) stfld native int class Curry<int32, int32, int32, int32>::f // curry.var = 1 ldloc.0 ldc.i4.1 stfld !0 class Curry<int32, int32, int32, int32>::var // bind = new Func<int32, int32, int32>(curry, curry.lambda) ldloc.0 ldftn instance !3 class Curry<int32, int32, int32, int32>::lambda(!1, !2) newobj instance void class [mscorlib]System.Func`3<int32, int32, int32>::.ctor(object, native int) stloc.1 // Console.WriteLine(bind(2, 3)) ldloc.1 ldc.i4.2 ldc.i4.3 callvirt instance !2 class [mscorlib]System.Func`3<int32, int32, int32>::Invoke(!0, !1) call void [mscorlib]System.Console::WriteLine(int32) ldstr "end..." call void [mscorlib]System.Console::WriteLine(string) ret }
関数の呼び出し方
関数はどうやって実行できるのか調べてみた
前にも調べたような気がするけど、忘れたのでもう一回
C#でInt->String->Intの関数を呼ぶ場合
delegate int F_dele(String s); static int f(String s) {return(999);} var x = new F_dele(f); Console.Write(x("Hoge"));
C#様に任せると大体こうなる
staticな関数だとF_dele.ctorの第一引数はldnullらしい
ldnull ldftn int32 f(string) newobj instance void F_dele.ctor(object, native int) stloc x ldloc x ldstr "Hoge" callvirt instance int32 F_dele::Invoke(string) call void Console::WriteLine(int32)
delegateはMulticastDelegateを継承したクラスになるが、正直よくわかんない
呼び出したい型ごとにクラス作って、ldftnした結果をいれるのはじゃんくさい
ldftnした結果の型を管理を自分でやるならこれで十分
ldftn int32 f(string) stloc x ldstr "Hoge" ldloc x calli int32 (string) call void Console::WriteLine(int32)
delegate int F_dele(String s); virtual int f(String s) {return(999);} var self = new Self(); var x = new F_dele(self.f); Console.Write(x("Hoge"));
メソッドだとF_dele.ctorの第一引数はレシーバっぽい
virtualじゃないメソッドなら当然ldftnになる
newobj Self::.ctor stloc self ldloc self ldvirtftn int32 f(string) newobj instance void F_dele.ctor(object, native int) stloc x ldloc x ldstr "Hoge" callvirt instance int32 F_dele::Invoke(string) call void Console::WriteLine(int32)
メソッドをldftnするとcalliの第一引数をレシーバにせんとならん
ってことはILに落とす前にこれは関数orメソッドだかんねと言う情報をもたせんとならん
ついでにメソッドを高階関数にすんのがめんどい
そのせいかldftnをdelegateでラップして渡すのがC#様やVB様のやり方くさい
デリゲートなら定義するときにldftn/ldvirtftnを切り替えてレシーバ渡すだけになる
関数を呼ぶところはcallvirtに統一できる
構造体が書けた
構造体が書けた
構造体そのものは簡単だったけど総称型を突っ込んだら内部構造色々変えないとうまくいかず面倒
こっちは当面ほっといて先にクロージャを実装しようかなぁ
とりあえずドラゴンクエストビルダーズが発売される前に一通りやっちゃわないとほっといて忘れそうな気がするので、困ったなぁ
前やってたこれが動けばとりあえずいいや
sub f(x: Int) #(=>Int) #return(f2) f2() sub f2() f3() sub f3() print(x) #f1(1)() f1(1)
ノード図はもうちょっとコンパクトにしたい
LetNodeとTypeNodeは文字列で書いちゃってもええかもしらん