※ 今しがた、親と付き合って酒をしこたま飲んだので、正しいことかいてるかは(いつも以上に)保証できません。日本語がおかしいかもしれません。
以上(1, 2)を踏まえて考えてみると、以下のような、プリミティブな関数をもう一個追加すれば、とても見通しが良くなる。
wrap :: Lang -> Computer Lang wrap l = Run $ \p -> case p of P l' -> (I l p l', l) I l' _ _ -> (I l p l', l)
wrap(ネーミングセンスへの突っ込みはご勘弁を…) は、与えられたプログラム p に対して、言語 l での解釈を付与する。これは run (付与された解釈を実行する)と対になっていて、同じ言語で wrap をして run をすると元に戻る ((wrap >>= run) は return と等しい)。言い換えれば、モナド上で、wrap は run に対する右単位射右逆射 (section、切断) になってる。
※ 上の表現がわかりづらければ、wrap は「言語 l でコード化する」あるいは「言語 l のプログラムに直す」、run が「プログラムを実行する」と読み替えても良い。(wrap じゃなくて encode って名前の方が良かったかも…)
この wrap と run の組み合わせで、 インタプリタもコンパイラも、モナドの操作として、かなりシンプルに書きなおせる。
-- インタプリタ (シンプル版) interpret2 :: Lang -> Lang -> Computer Lang interpret2 m l = run l >> wrap m -- コンパイラ (シンプル版) compile2 :: Lang -> Lang -> Lang -> Computer Lang compile2 m t s = run s >> wrap t >> wrap m
どちらも、入力プログラムを仮定して、それをいっぺん run した後に
これは、sumiiくんが初めに言っていた、「インタプリタ≒コンパイラ+実行系」、って言明の補強にもなってる。run を実行系と解釈すれば、run を後ろにくっつける = 後ろの wrap を一個削る なので。(追記:ただし厳密には、「run_M o C」と言うのはあまり正しくないかも。以下注釈。*1)
で、いずれにせよ、あるいはインタプリタやコンパイラを何重に重ねた場合でも、元の入力プログラムを動作させるには、wrap されている分だけ run させればよい。
…多分。