回答:
高次関数を実装するには、3つの主なアプローチ(私が知っている)があります。非機能化、クロージャ変換/ラムダリフティング、コンビネータ。
レッツ・書き込みから高次機能のタイプのにとのタイプのCスタイルの関数ポインタからに。(これを形式化したい場合、関数ポインターの抽象化は空の環境でのみ許可されると言えます。)
クロージャ変換は、私たちが表現選ぶという考えである一般的にラムダのサイトで自由変数の値を保持するタプルになりますが抽象化。それは他の表現かもしれません。
ラムダリフティングは、やや異なる、よりグローバルなアプローチをとります。ラムダ抽象化は、最上位のスコープに到達するまで、途中でパラメーターとして自由変数を追加するスコープに包含されます。これはブロック構造化を扱いますが、実際に関数の高次の使用を処理するには、部分的な適用を許可する必要があります。その後、部分的に適用された関数を渡すことができますが、これは基本的にクロージャ変換と同じ表現です。
関数ポインタを削除したい場合は、機能停止を使用できます。この特殊なケースでは、列挙が生成されます。ただし、ほとんどのアセンブリ言語では関数ポインタが自然な構成要素であるため、これを行う理由はほとんどありません。
次のアプローチは、コンビネーターを使用することです。これは基本的に、ラムダリフティングや部分的なアプリケーションの使用と同じですが、トップレベル関数の固定セットが使用され、他のすべての関数はそれらの組み合わせとして表現されます。(コンビネータの事前定義された固定セットがない場合、これは通常、上記で説明したようなラムダリフティングベースのアプローチです)。高次関数は、Haskell構文を使用してデータ型の値によって効果的に表されます。次のように(SK
コンビネータを使用):
data CA = S | K | App CA CA -- plus other things in reality, like primitive values
脊柱結石に近い表現は、おそらく効率にとってより理にかなっています。または、次のようにすることもできます。
data CA = S0 | S1 CA | S2 CA CA | K0 | K1 CA
高次関数を適用すると、2つのケースに分かれます。コンビネーターが完全に適用されて実行されるか、または(部分的な)アプリケーションを表す新しい値を返します。
私は徹底的な調査を行っていませんが、クロージャ変換のバリエーションは、高次関数の最も一般的な実装戦略であると確信しています(そのため、これらはしばしば「クロージャ」と呼ばれます)。それは、その単純な形でさえ、モジュール式でシンプルであり、かなり効率的であるという素晴らしい特性を持っています。ベースコンビネーターを適切に選択し、コンビネーターベースのアプローチを適切に実行するには、ある程度の賢さが必要です。非機能化は、私が知る限り、広く使用されているわけではありませんが、関数ポインターを利用しない理由はほとんどありません。たとえば、大規模なケース分析の代わりに、インデックスを作成する関数ポインターのテーブルがある場合は、基本的にクロージャー変換を再作成しました。
他にもいくつかのアプローチがあります。1つは、テンプレートのインスタンス化です。これは、基本的に -reductionを文字どおりに使用し、単に文字通りに用語を他の用語に置き換えます。通常、これには、抽象構文のツリーのような構造が必要です。次に、高次関数は、それらの(構文)ラムダ項、または置換の実行を簡略化できる「テンプレート」によって表されます。