私がやっていること:ファイルを解析し、それを一連の操作に変換し、数千のデータセットをそのシーケンスにフィードして、それぞれから最終的な値を抽出できる小さなインタープリターシステムを作成しています。コンパイル済みインタープリターは、データセットと実行コンテキストの 2 つの引数を取る純粋な関数のリストで構成されます。各関数は、変更された実行コンテキストを返します。
type ('data, 'context) interpreter = ('data -> 'context -> 'context) list
コンパイラは基本的に、次のように定義されたマップ記述を使用する最終的なトークンから命令へのマッピング ステップを備えたトークナイザーです。
type ('data, 'context) map = (string * ('data -> 'context -> 'context)) list
一般的なインタープリターの使用法は次のようになります。
let pocket_calc =
let map = [ "add", (fun d c -> c # add d) ;
"sub", (fun d c -> c # sub d) ;
"mul", (fun d c -> c # mul d) ]
in
Interpreter.parse map "path/to/file.txt"
let new_context = Interpreter.run pocket_calc data old_context
問題:、、メソッド、および対応する型 (あるコンテキスト クラスでは整数、別のコンテキスト クラスでは浮動小数点数など)pocket_calcをサポートする任意のクラスでインタープリターを動作させたいと考えています。addsubmuldata
ただし、pocket_calcは関数ではなく値として定義されているため、型システムはその型をジェネリックにしません。最初に使用すると、'dataと'context型は最初に提供したデータとコンテキストの型にバインドされ、インタープリターは次のようになります。他のデータおよびコンテキスト タイプとは永遠に互換性がありません。
実行可能な解決策は、インタープリターの定義を eta-expand して、その型パラメーターをジェネリックにできるようにすることです。
let pocket_calc data context =
let map = [ "add", (fun d c -> c # add d) ;
"sub", (fun d c -> c # sub d) ;
"mul", (fun d c -> c # mul d) ]
in
let interpreter = Interpreter.parse map "path/to/file.txt" in
Interpreter.run interpreter data context
ただし、この解決策はいくつかの理由で受け入れられません。
呼び出されるたびにインタープリターを再コンパイルするため、パフォーマンスが大幅に低下します。マッピング ステップ (マップ リストを使用してトークン リストをインタープリターに変換する) でさえ、顕著な速度低下を引き起こします。
私の設計は、初期化時に読み込まれるすべてのインタープリターに依存しています。これは、読み込まれたファイルのトークンがマップ リストの行と一致しない場合にコンパイラが警告を発行するためです。インタプリタは最終的に実行されます)。
特定のマップ リストを複数のインタープリターで再利用したい場合があります。単独で使用する場合もあれば、追加の命令 (たとえば
"div") を先頭に追加する場合もあります。
質問: eta-expansion 以外に型をパラメトリックにする方法はありますか? モジュールの署名や継承に関係する巧妙なトリックでしょうか? それが不可能な場合、eta-expansion を受け入れられる解決策にするために、上記の 3 つの問題を軽減する方法はありますか? ありがとうございました!