1パスの正規形変換を実現するために必要となるプログラミング手法を説明するため,ここでは同じ手法を使った変換先コードのちょっとした効率化を行ってみる.
まず,2パスで変換する場合の1つ目のパスである変換のうち,本質的な箇所であるを講義テキストから転載する.ただし,講義テキストでは言語上で行う最適化の簡単化のため,を用いてプログラム中のあらゆる名前をフレッシュな名前へ付け替えていたが,この資料のコンパイラではこの段階での名前の付替えは必要ないため省略している.また,真偽値true
とfalse
はそれぞれ1
と0
に置き換えられるため省略し,さらに(講義テキスト5章のMLでは式ではなくトップレベル宣言であった)let rec
を追加している.
さて,次の式:
に変換を用いると:
となることは,すぐに分かるはずである.しかし,正規形や言語においてif
式の制限は「条件節が値であること」であって,この場合1
は最初から値なのだから,よりコンパクトな:
で良いはずである.つまり,条件節がすでに値であるかどうかに関わらず,必ずあらたな変数束縛を導入しているのが無駄である.また,ここではif
式を例に取り上げて説明しているが,二項演算式や関数適用式等でもこのような無駄な束縛がまったく同様にして起こり得る.
上の例のように,条件節に埋め込みたい式が値かどうかに従って異なる変換先コードをつくる方法について考えてみよう.まず,が値の場合には,通常の代入:
を行うだけである.
一方,が値ではない場合には,適当なフレッシュ変数を用意し,正規形への変換で説明した特殊代入:
を行う.代入される式の形から,特殊代入における特別扱いが必ずちょうど1回だけ起こることに注意して欲しい.
ここまではif
式を例に説明してきたが,先程も述べたとおり,一般的には二項演算式や関数適用式なども考慮する必要がある.そこで,ある式(の内部)への通常の代入を関数で抽象化する.
たとえば,上の例のif
式への通常の代入であれば:
と定義でき,この関数をを引数にして呼び出すと代入結果のif
式が返される.
最終的に,式と関数の2つを受け取り,が値かどうかに従って上記いずれかの代入を行う関数は:
と定義できる.
変換との処理を同時に行う(つまり,中の適切な箇所で適切な引数をつくって関数を呼び出す)ようなOCaml関数を書くことはそれほど難しくなく,そうすることで冒頭で述べたちょっとした効率化が1パスの変換で達成される.
最後に,この付録の問題と課題5との関係について触れておく.この付録では:
if
式中のvalue
型のコードしか埋め込めないところへ,exp
型のコードを埋め込もうとするのを扱っているのに対し,課題5では:
let
式中のcexp
型のコードしか埋め込めないところへ,exp
型のコードを埋め込もうとするのを扱っていると言える.