resolveとclosureのまとめ
Giraffe+のScript\samples\resolve_and_closure.giraffeにテストとしてまとまってますが、resolve, resolve2, closure, closure2, closure3と十分ややこしくなってきてるので解説しておきます。
LispやJavaScriptのようなクロージャはgiraffeにはありません。コンテキストの継承により、クロージャ無しでも問題無い場面が多いためです。
Container.each等の、引数のメソッドをすぐに実行(invoke)する普通の高階関数はコンテキストを継承して実行しますので心配ありませんが、そうしない高階関数の場合はそれを意識しなければいけません。
指定した名前を解決するのがMethod.resolve, コンテキストをmethodに結びつけるのがMethod.closureです。(Block.resolveもありますが、Methodのものと同じなので解説は省きます。)
Method.resolve
指定した名前を解決したMethodオブジェクトを返します。
x: 0 `[ x ].resolve(:x).flush //`[0]
第二引数を指定した場合はその値になります。複数の名前を解決したい場合は複数回呼ぶ必要があります。
x: 0 y: 1 `[ x y ].resolve(:x).resolve(:y).flush //`[0 1]
Method.resolve2
名前の指定をMethodリテラル側でセミコロンを用いてします。
x: 0 `[ ;x ].resolve2.flush //`[0]
こちらのほうがLispのマクロのバッククォート構文のような感じになります。複数の名前を解決する場合も同じです。
x: 0 y: 1 `[ ;x ;y ].resolve2.flush //`[0 1]
Method.resolve3 (undefined)
そのコンテキストからアクセス可能な全ての名前を解決するresolve3があってもよさそうに感じますが、現状ではとにかくありません。それが自動的に呼ばれればJavaScriptと同じ感覚で扱えることになりますが、とにかく今はありません。
理由は、実行するまでどのスロット(変数)がそのメソッドの実行時に定義されるか分からないためです。
x: 0 `[ x: 1 x ].resolve3
この場合、ようするに、resolve(:x)した場合と同じ動作をした場合、x: 1は無意味です。
もちろんこの例においてはresolve3がパースのようなことをしてx: 1を検出することも可能ですが、他コンテキストから変数を定義することすら可能なこの言語ではそれは無理があります。JavaScriptと同じようなクロージャを望むならMethod.closure2を使うことになります。
Method.closure
指定したコンテキスト(デフォルトでlocal)を実行時に継承するMethodオブジェクトを返します。
x: 0 `[ x ].closure.().flush //0 (.()はコンテキストを継承しない.invoke)
コンテキスト(とそれが持つ実引数オブジェクト)には寿命があります。そのメソッドの実行が終われば即死します。これもGC管理にすればこの問題は解決しますが、パフォーマンス上の問題によりしてません。コンテキストの寿命が問題になる場合はclosure2を使います。
Method.closure2
指定したコンテキスト(デフォルトでlocal)からアクセス可能なスロットを全て持つコンテキストを実行時に継承するMethodオブジェクトを返します。
スロットだけですので、self等は扱えません。parentが元来の呼び出し時のコンテキストです。closure2は保持したスロットをそのコンテキストに実行時に挿入します。
Method.closure3
Method.closure2にself等も加えたものです。引数も対象になることに留意が必要です。引数にはparentでアクセスできます。
`[ parent.arg.{0} ].closure3.(1).flush //1 (このコンテキストに引数が無いため、引数を与えてもエラーにならない)