ラムダリフティング

ラムダリフティングは、コンピュータプログラムを再構成し、関数がグローバルスコープ内で互いに独立して定義されるようにするメタプロセスです。個々のリフティングは、ローカル関数(サブルーチン)をグローバル関数に変換します。これは以下の2段階のプロセスです。

  • パラメータを追加して関数内の自由変数を削除します
  • 関数を制限されたスコープからより広いスコープまたはグローバル スコープに移動します。

「ラムダリフティング」という用語は、1982年頃にトーマス・ジョンソンによって初めて導入され、歴史的には関数型プログラミングに基づくプログラミング言語を実装するためのメカニズムと考えられてきました。一部の最新のコンパイラでは、他の手法と組み合わせて使用​​されています

ラムダリフトはクロージャ変換とは異なります。ラムダリフトでは、すべての呼び出し箇所を調整する必要があり(呼び出しに引数(パラメータ)を追加する)、リフトされたラムダ式にクロージャは導入されません。一方、クロージャ変換では、呼び出し箇所を調整する必要はなく、自由変数を値にマッピングするラムダ式にクロージャが導入されます。

この手法は、コード リファクタリングで個々の関数に使用して、関数をそれが書かれたスコープ外でも使えるようにすることができる。 ラムダ リフトはプログラムを変換するために繰り返すこともできる。 リフトを繰り返して使用すると、ラムダ計算で書かれたプログラムを、ラムダを使わない再帰関数のセットに変換することができる。 これは、ラムダ計算で書かれたプログラムと関数として書かれたプログラムの等価性を示している。[1] しかし、これは演繹におけるラムダ計算の健全性を示すものではない。ラムダ リフティングで使用されるイータ リダクションは、変数の条件を満たす値が 1 つだけであることを最初に確認せずに変数から値を削除するため、ラムダ計算に基数の問題をもたらすステップだからである(カリーのパラドックスを参照)。

ラムダリフティングはコンパイラの処理時間を増大させます。ラムダリフティングの効率的な実装はコンパイラの処理時間を短縮します。[2]

型なしラムダ計算では、基本型は関数であるため、ラムダ式のベータ縮約の結果は、リフティングによって変化する可能性があります。結果として得られる関数は数学的には同じ意味を持ちますが、型なしラムダ計算では同じ関数とはみなされません。内包的等価性と外延的等価性も参照してください。

ラムダリフティングの逆の操作はラムダドロップである。[3]

ラムダドロップは、コンパイラによるプログラムのコンパイル速度を向上し、パラメータ数とスタックフレームのサイズを削減することで、結果のプログラムの効率を向上させる可能性があります。しかし、関数の再利用は困難になります。ドロップされた関数はコンテキストに結び付けられており、別のコンテキストで使用するには、まず関数を解放する必要があります。

アルゴリズム

次のアルゴリズムは、クロージャをファーストクラスオブジェクトとしてサポートしていない言語で任意のプログラムをラムダリフトする 1 つの方法です

  1. 各関数に一意の名前が付くように関数の名前を変更します。
  2. 各自由変数を囲む関数への追加引数に置き換え、その引数を関数を使用するたびに渡します。
  3. 自由変数を持たないすべてのローカル関数定義を同一のグローバル関数に置き換えます。
  4. すべての自由変数とローカル関数が削除されるまで、手順 2 と 3 を繰り返します。

言語に、引数として渡したり他の関数から返したりできるファーストクラス オブジェクトとしてクロージャがある場合、クロージャは、自由変数のバインディングをキャプチャするデータ構造によって表現する必要があります。

次のOCamlプログラムは、1 から 100 までの整数の合計を計算します。

rec  sum  n  =  if n = 1 then 1 else let f x =  n + x in f ( sum ( n - 1 ) ) sum 100                    

( は自身を呼び出す可能性のある関数としてlet rec宣言されていますsum。)関数 f は、sum の引数に引数より小さい数値の合計を加算する関数であり、局所関数です。f の定義において、n は自由変数です。まず、自由変数を引数に変換します。

rec  sum  n  =  if n = 1 then 1 else let f w x = w + x in f n  ( sum ( n - 1 ) ) sum 100                      

次に、f をグローバル関数に持ち上げます。

rec  f  w  x  = w + xとし、sum n = if n = 1 then 1 else f n ( sum ( n - 1 )) in sum 100 とする                      

以下は同じ例ですが、今回はJavaScriptで記述されています。

// 初期バージョン関数sum ( n ) {関数f ( x ) { return n + x ; }           n == 1場合は1 を返しますそれ以外の場合はf ( sum ( n - 1 ) )を返します。 }          // 自由変数nを仮パラメータwに変換した後関数sum ( n ) {関数f ( w , x ) { return w + x ; }            if ( n == 1 )は1 を返しますそうでない場合はf ( n sum ( n - 1 )を返します。 }           // 関数fをグローバルスコープに持ち上げた後関数f ( w , x ) {戻り値w + x ; }       関数sum ( n ) { if ( n == 1 ) return 1 ; else return f ( n , sum ( n - 1 )); }              

ラムダリフティングとクロージャ

ラムダリフトとクロージャはどちらもブロック構造のプログラムを実装するための手法です。ブロック構造を解消することでブロック構造を実装します。すべての関数はグローバルレベルにリフトされます。クロージャ変換は、現在のフレームを他のフレームにリンクする「クロージャ」を提供します。クロージャ変換はコンパイル時間を短縮します。

再帰関数やブロック構造のプログラムは、リフティングの有無にかかわらず、スタックベースの実装を使用して実装できます。これはシンプルで効率的です。ただし、スタックフレームベースの実装は厳密(eager)である必要があります。スタックフレームベースの実装では、関数の寿命は後入先出(LIFO)である必要があります。つまり、計算を開始した最も新しい関数が、最初に終了する必要があります。

一部の関数型言語( Haskellなど)は、遅延評価を用いて実装されています。遅延評価とは、値が必要になるまで計算を遅らせるものです。この遅延実装戦略は、プログラマに柔軟性を提供します。遅延評価では、関数によって計算された値が要求されるまで、関数の呼び出しを遅らせます。実装方法の一つとして、値の代わりに、計算内容を記述したデータの「フレーム」への参照を記録する方法があります。後で値が必要になったときに、そのフレームを使って必要なタイミングで値が計算されます。そして、計算された値が参照を置き換えます。

「フレーム」はスタックフレームに似ていますが、スタックに格納されない点が異なります。遅延評価では、計算に必要なすべてのデータがフレームに保存される必要があります。関数が「リフト」されている場合、フレームには関数ポインタと関数へのパラメータのみを記録します。一部の最新言語では、変数の寿命を管理するために、スタックベースの割り当ての代わりにガベージコレクションを使用しています。管理されたガベージコレクション環境では、クロージャは値を取得できるフレームへの参照を記録します。一方、リフトされた関数は、計算に必要な各値に対してパラメータを持ちます。

Let式とラムダ計算

Let式は、リフティング、ドロップ、そして再帰方程式とラムダ式の関係を記述するのに役立ちます。ほとんどの関数型言語にはlet式があります。また、ALGOLPascalのようなブロック構造のプログラミング言語も、制限されたスコープ内での使用のために関数のローカル定義を許容するという点でlet式に似ています

ここで使用されるlet式、多くの関数型言語で実装されているlet recの完全な相互再帰バージョンです。

let式はラムダ計算と関連しています。ラムダ計算は構文と意味論が単純で、ラムダリフティングの記述に適しています。ラムダリフティングはラムダからlet式への変換として、ラムダドロップはその逆として記述すると便利です。これはlet式が相互再帰を許容するためであり、ある意味ではラムダ計算でサポートされているよりもリフティングが進んでいると言えます。ラムダ計算は相互再帰をサポートしておらず、最も外側のグローバルスコープで定義できる関数は1つだけです。

持ち上げずに平行移動を記述する変換規則は、 Let 式の記事に記載されています。

以下の規則は、lambda式とlet式の等価性を説明するものである。

名前
イータ還元等価性
Let-Lambda同値性
組み合わせてみましょう

ラムダ式のリフティングとドロップを記述するメタ関数を示します。メタ関数とは、プログラムをパラメータとして受け取る関数です。プログラムはメタプログラムのデータです。プログラムとメタプログラムは異なるメタレベルにあります。

プログラムとメタプログラムを区別するために、以下の規則が使用されます。

  • 角括弧 []は、メタプログラム内の関数の適用を表すために使用されます。
  • メタプログラム内の変数には大文字が使用されます。プログラム内の変数は小文字で表されます。
  • メタプログラム内のequalsに使用されます。
  • ダミー変数、つまり未知の値を表します。

簡潔にするために、最初のルールが適用されます。また、これらのルールでは、ラムダ式が前処理され、各ラムダ抽象化に一意の名前が付けられていることを前提としています。

置換演算子は広く用いられています。式は、L内のGをすべてSに置き換え、その結果を返すことを意味します。ここで用いる定義は、ラムダ計算のページに記載されている定義を拡張し、式の置換もカバーするようにしています。式のマッチングは、式のアルファ同値性(変数名の変更)を比較することで行います。

ラムダ計算におけるラムダリフティング

各ラムダリフトは、ラムダ式の部分式であるラムダ抽象化を取り、それを関数呼び出し(適用)に置き換えます。部分式内の自由変数は、関数呼び出しのパラメータとなります。

コードリファクタリングにおいて、個々の関数に対してラムダリフトを用いることで、関数をそのスコープ外でも使用可能にすることができます。また、ラムダリフトは、式にラムダ抽象がなくなるまで繰り返し適用することで、プログラムを変換することもできます。

ラムダリフト

リフトは、式内の部分式をその式の先頭まで持ち上げる操作です。式はより大きなプログラムの一部である場合もあります。これにより、部分式をどこに持ち上げるかを制御できます。プログラム内でリフトを実行するために使用されるラムダリフト演算は、以下のとおりです。

サブ式は、ラムダ抽象化、またはパラメータに適用されたラムダ抽象化のいずれかになります。

2種類のリフトが可能です。

  • 匿名リフト
  • 名前付きリフト

匿名リフトは、ラムダ抽象化のみのリフト式を持ちます。これは匿名関数の定義とみなされます。関数には名前を付ける必要があります。

名前付きリフト式は、式にラムダ抽象化を適用したものです。このリフトは関数の名前付き定義とみなされます。

匿名リフト

匿名リフトはラムダ抽象化(Sと呼ばれる)を取ります。Sの場合;

  • S置き換える関数(V )の名前を作成します。V で識別される名前が使用されていないことを確認してください。
  • S内のすべての自由変数のパラメータをVに追加して、式Gを作成します( make-call を参照)。

ラムダ リフトは、関数定義の追加とともに、関数適用 をラムダ抽象化Sに置き換えるものです。

新しいラムダ式では、GがSに置き換えられています。L [ S := G ]は、LGがSに置き換えられることを意味します。関数定義には関数定義G = Sが追加されています。

上記の規則において、Gは式Sに代入される関数適用であり、以下のように定義される。

ここで、Vは関数名です。これは新しい変数、つまりラムダ式でまだ使用されていない名前である必要があります。

ここで、 はEで使用される変数のセットを返すメタ関数です

匿名リフトの例。
例えば、

ラムダ式からlet式への変換de-lambdaを参照してください。結果は次のようになります。

呼び出しの構築

関数呼び出しGは、自由変数セット(Vで表される)内の各変数のパラメータを関数Hに追加することによって構築されます。

呼び出し構築の例。

名前付きリフト

名前付きリフトは、関数名Vが提供される点を除いて、匿名リフトと似ています。

匿名リフトに関しては、式GはSの自由変数を適用することでVから構築される。これは次のように定義される。

名前付きリフトの例。

例えば、

ラムダ式からlet式への変換de-lambdaを参照してください。結果は次のようになります。

与える、

ラムダリフト変換

ラムダリフト変換は、ラムダ式を受け取り、すべてのラムダ抽象を式の先頭まで持ち上げます。その後、これらの抽象は再帰関数に変換され、ラムダ抽象は除去されます。その結果、以下の形式の関数型プログラムが得られます。

ここで、Mは一連の関数定義であり、Nは返される値を表す式です。

例えば、

その後、 de -letメタ関数を使用して結果をラムダ計算に戻すことができます。

ラムダ式を変換する処理は一連のリフトである。各リフトには、

  • 関数lift-choiceによって選択された部分式。部分式は、ラムダを含まない方程式に変換できるように選択する必要があります。
  • リフトは、次のセクションで説明するlambda-liftメタ関数の呼び出しによって実行されます。

リフトが適用されると、レットは 1 つのレットに結合されます。

次に、パラメータドロップを適用し、「let」式で不要なパラメータを削除します。let式では関数定義が互いに直接参照できますが、ラムダ式抽象化は厳密に階層化されており、関数は自身を直接参照することはできません。

リフティングの表現の選択

式をリフト対象として選択する方法は2つあります。1つ目は、すべてのラムダ抽象化を無名関数の定義として扱います。2つ目は、パラメータに適用されるラムダ抽象化を関数の定義として扱います。パラメータに適用されるラムダ抽象化は、関数を定義するlet式と無名関数の定義の2つの解釈が可能です。どちらの解釈も有効です。

これら 2 つの述語は、両方の定義に必要です。

ラムダフリー- ラムダ抽象化を含まない式。

lambda-anon - 匿名関数。Xがラムダフリーであるような式。

持ち上げのためだけに匿名関数を選択する

最も深い匿名抽象化を探索します。これにより、リフトを適用した関数は単純な方程式になります。この定義では、パラメータを持つラムダ抽象化は関数の定義とはみなされません。すべてのラムダ抽象化は匿名関数の定義とみなされます。

lift-choice - 式を走査して見つかった最初の匿名関数、または関数がない場合はnone 。

例えば、

ラムダの選択
ルール関数型選択
2
3
1匿名
ラムダの選択
ルール関数型選択
2匿名
2
持ち上げるための名前付き関数と匿名関数の選択

最も深い名前付き関数または無名関数の定義を検索します。これにより、リフトを適用した関数は単純な式になります。この定義は、実引数を持つラムダ抽象化を関数定義として認識します。適用のないラムダ抽象化のみが無名関数として扱われます。

ラムダ名
名前付き関数。Mはラムダフリー、N はラムダフリー、または無名関数のような式。
リフト選択
式を走査して見つかった最初の匿名関数または名前付き関数、または関数がない場合はnone 。

例えば、

ラムダの選択
ルール関数型選択
2
1名前
ラムダの選択
ルール関数型選択
1匿名

例えば、Yコンビネータ

次のように解除されます。

パラメータを削除した後、

ラムダ式として(let式からラムダ式への変換を参照)、

名前付き関数と匿名関数の持ち上げ
ラムダ式関数から変数
1真実
2

3
4
5

匿名関数のみを持ち上げる場合、Yコンビネータは、

パラメータを削除した後、

ラムダ式として、

匿名関数のみを持ち上げる
ラムダ式関数から変数
1真実
2
3
4
5

リフトのために選択される最初の部分式は です。これにより、ラムダ式が に変換され、方程式 が作成されます

2番目に選択される部分式は です。これにより、ラムダ式は に変換され、方程式 が作成されます

そしてその結果は、

驚くべきことに、この結果は、名前付き関数の持ち上げから得られる結果よりも単純です。

実行

Kに関数を適用すると

それで、

または

Yコンビネータは、そのパラメータ(関数)を自身に対して繰り返し呼び出します。関数が固定小数点を持つ場合、値は定義されます。ただし、関数は決して終了しません。

ラムダ計算におけるラムダドロップ

ラムダドロップ[4]とは、関数のスコープを狭め、そのスコープのコンテキストを利用して関数のパラメータ数を減らす手法です。パラメータ数を減らすことで、関数の理解が容易になります。

ラムダリフティングのセクションでは、まずラムダ式をリフティングし、その結果得られたラムダ式を再帰方程式に変換するメタ関数について説明しました。ラムダドロップメタ関数は、その逆の処理を行います。まず再帰方程式をラムダ抽象化に変換し、その結果得られたラムダ式をラムダ抽象化へのすべての参照をカバーする最小のスコープにドロップします。

ラムダドロップは2つのステップで実行されます。

  • 沈没
  • パラメータの削除

ラムダドロップ

ラムダドロップは、プログラムの一部である式に適用されます。ドロップは、ドロップが除外される一連の式によって制御されます。

どこ、

Lは削除されるラムダ抽象化です。
Pはプログラムです
Xは、削除から除外される式のセットです。

ラムダドロップ変換

ラムダドロップ変換は、式内のすべての抽象化をシンクします。式セット内の式からはシンクは除外されます。

どこ、

Lは変換される式です。
Xは、ドロップから除外されるサブ式のセットです。

sink-tranは各抽象化を最も内側から順にシンクします。

抽象化の沈没

シンクとは、ラムダ抽象化を可能な限り内側に移動させることですが、それでも変数へのすべての参照の外側に残ります。

応用例-4件。

抽象化。名前の変更を使用して、変数名がすべて異なることを確認します。

変数- 2 件。

シンクテストは式をドロップから除外します。

沈没の例

例えば、

ルール表現
解除
シンクトラン
応用
変数
応用
変数
抽象化
応用

パラメータの削除

パラメータドロップとは、関数内の位置に合わせて関数を最適化することです。ラムダリフトでは、関数をコンテキスト外に移動するために必要なパラメータが追加されました。ドロップでは、このプロセスが逆になり、自由変数を含む余分なパラメータが削除される可能性があります。

パラメータの削除とは、関数から不要なパラメータを削除することです。この場合、実際に渡されるパラメータは常に同じ式です。式の自由変数は、関数が定義されている箇所でも自由である必要があります。この場合、削除されたパラメータは関数定義本体内の式に置き換えられます。これにより、パラメータは不要になります。

例えば、

この例では、仮引数oの実引数は常にpです。p は式全体の中で自由変数であるためこの引数は省略できます。仮引数yの実引数は常にnです。しかし、nはラムダ抽象化によって束縛されているため、この引数は省略できません。

パラメータを削除した結果は、

主な例として、

drop-params-tranの定義は、

どこ、

パラメータリストを作成する

関数を定義する抽象化ごとに、名前を削除するかどうかの判断に必要な情報を構築します。この情報は、各パラメータについて記述します。パラメータ名、実際の値を表す式、そしてすべての式が同じ値を持つことを示す情報です。

例えば、

関数gのパラメータは、

形式パラメータすべて同じ値実際のパラメータ式
×間違い_
o真実p
y真実n

各抽象化は一意の名前に変更され、パラメータリストは抽象化の名前に関連付けられます。例えば、gにはパラメータリストがあります。

build-param-lists は、式を走査することで、式に対応するすべてのリストを構築します。4つのパラメータがあります。

  • 分析対象のラムダ式。
  • 名前のテーブルパラメータリスト。
  • パラメータの値の表。
  • 返されるパラメータリストは、

抽象化- 形式のラムダ式を分析して、関数のパラメータの名前を抽出します。

名前を検索し、その名前に対応するパラメータリストの構築を開始します。仮パラメータ名を入力します。また、式の本体から実パラメータリストを受け取り、それをこの式の実パラメータリストとして返します。

変数- 関数の呼び出し。

関数名またはパラメータの場合は、この名前のパラメータ リストを出力することによって、実際のパラメータ リストの入力を開始します。

アプリケーション- アプリケーション (関数呼び出し) が処理され、実際のパラメータの詳細が抽出されます。

式とパラメータのパラメータリストを取得します。式のパラメータリストからパラメータレコードを取得し、現在のパラメータ値がこのパラメータと一致することを確認します。パラメータ名の値を記録しておき、後で確認に使用します。

上記のロジックは、その動作が非常に微妙です。同一値インジケータがtrueに設定されることはありません。すべての値が一致しない場合にのみfalseに設定されます。値は、Sを使用して、 Sに許容されるブール値の集合を構築することで取得されます。trueがメンバーである場合、このパラメータのすべての値は等しいため、パラメータは削除できます。

同様に、def は集合論を使用して、変数に値が与えられているかどうかを照会します。

Let - Let 式。

And - 「let」で使用します。

例えば、パラメータリストを構築する場合、

与える、

パラメータoは省略され、

ビルドパラメータリスト
ビルドパラメータリストの例
ルール表現
抽象化
抽象化
ルール表現
パラメータを追加

パラメータを追加

パラメータを追加

リスト終了
ルール表現
応用
応用

変数

変数

与える、

ルール表現
応用

アプリケーション、変数

アプリケーション、変数

変数

ルール表現
応用

アプリケーション、変数

アプリケーション、変数

変数

の定義がないので、equateは次のように簡略化できる。

不要な表現を削除することで、

の2つの式を比較すると

が真の場合;

が偽の場合、何の含意もありません。つまり、真か偽かのどちらかです。

が真の場合;

が真の場合;

それは誤りです。

その結果、

ルール表現
応用
応用

変数

上記と同様の議論により、

そして以前から、

もう一つの例は、

ここでxはfに等しい。パラメータリストのマッピングは、

そしてパラメータxは省略され、

ビルドパラメータリスト

このより難しい例では、 equateのロジックが使用されています。

ルール表現
抽象化
抽象化
ルール表現
パラメータを追加
パラメータを追加
リスト終了
ルール表現
抽象化
応用
名前
名前

名前
応用
名前
ルール表現
抽象化
応用
名前
名前
名前
応用
名前
名前

結果をまとめて、

の 2 つの定義から;

それで

使用し

上記と比較すると、

それで、

で、

次のように減少する。

また、

次のように減少する。

したがって、p のパラメータ リストは実質的に次のようになります。

ドロップパラメータ

ビルドパラメータリストによって得られた情報を使用して、不要になった実際のパラメータを削除します。drop -paramsには、

  • パラメータを削除するラムダ式。
  • 変数名とパラメータ リスト (ビルド パラメータ リストに組み込まれている) のマッピング。
  • ラムダ式内の変数のセットを解放します。
  • 返されるパラメータリスト。アルゴリズム内部で使用されるパラメータ。

抽象化

どこ、

どこ、

変数

関数名またはパラメータの場合は、この名前のパラメータ リストを出力することによって、実際のパラメータ リストの入力を開始します。

アプリケーション- アプリケーション(関数呼び出し)が処理され、

Let - Let 式。

And - 「let」で使用します。

アプリケーションからパラメータを削除する
状態表現

パラメータリストの構築結果から;

それで、

それで、

状態拡大表現

状態拡大表現
V = \{p, q, m\}

形式パラメータを削除する

drop-formalは、ドロップリストの内容に基づいて仮パラメータを削除します。そのパラメータは以下のとおりです。

  • ドロップリスト、
  • 関数の定義 (ラムダ抽象化)。
  • 関数定義からの自由変数。

ドロップフォーマルとは、

これは次のように説明できる。

  1. すべての実際のパラメータが同じ値を持ち、その値のすべての自由変数が関数の定義に使用できる場合は、パラメータを削除し、古いパラメータをその値に置き換えます。
  2. それ以外の場合はパラメータを削除しないでください。
  3. それ以外の場合は関数の本体を返します。
状態表現

Yコンビネータの関数定義から始めると、

変換表現
要約* 4
ラムダ抽象トランザクション
シンクトラン
シンクトラン
ドロップパラメータ
ベータ-レデックス

Yコンビネータを返す

参照

参考文献

  1. ^ ジョンソン、トーマス (1985). 「ラムダリフティング:プログラムから再帰方程式への変換」. ジュアンノー、JP (編).関数型プログラミング言語とコンピュータアーキテクチャ. FPCA 1985 . コンピュータサイエンス講義ノート. 第201巻. シュプリンガー. CiteSeerX  10.1.1.48.4346 . doi :10.1007/3-540-15975-4_37. ISBN 3-540-15975-4
  2. ^ Morazán, Marco T.; Schultz, Ulrik P. (2008). 「Optimal Lambda Lifting in Quadratic Time」.関数型言語の実装と応用 ― 改訂選集. pp.  37– 56. doi :10.1007/978-3-540-85373-2_3. ISBN 978-3-540-85372-5
  3. ^ Danvy, O.; Schultz, UP (1997). 「ラムダドロップ」. ACM SIGPLAN Notices . 32 (12): 90– 106. doi : 10.1145/258994.259007 .
  4. ^ Danvy, Olivier; Schultz, Ulrik P. (2000年10月). 「ラムダドロップ:再帰方程式をブロック構造のプログラムに変換する」(PDF) .理論計算機科学. 248 ( 1–2 ): 243– 287. CiteSeerX 10.1.1.16.3943 . doi :10.1016/S0304-3975(00)00054-2. BRICS-RS-99-27. 
  • Stack Overflow での説明(JavaScript の例付き)
  • スロネガー、ケン、カーツ、バリー。「5. let式についての考察」(PDF)プログラミング言語の基礎. アイオワ大学。
Retrieved from "https://en.wikipedia.org/w/index.php?title=Lambda_lifting&oldid=1323097752"