MiniML言語のBNF文法を以下に示す.文法中,は非終端記号を表し,(空白を含まない文字列)は終端記号を表すものとする.は英小文字,数字,下線記号(_),単一引用符(')の列である.ただし,最初の文字は英小文字とする.は1個以上の数字の列である.終端記号の区切りは任意個のスペース,タブ,または改行とする.

MiniMLの文法規則

MiniML言語は,講義テキストのML言語と比較し,以下の3つの点で大きく異なる.

(1) トップレベル宣言

MiniML言語では,トップレベルの宣言 (declaration)を書くことはできず,プログラム全体は単一の (expression)からなる.トップレベルで宣言できないだけであり,let式やlet rec式による変数や関数の束縛は可能である.したがって:

let square = fun x -> x * x;;
let radius = 10;;
3 * square radius;;

のようなトップレベル宣言を含むMLのプログラムと同等のMiniMLプログラムは,単に:

let square = fun x -> x * x in
let radius = 10 in
3 * square radius;;

と書けば良く,言語の表現力はまったく損われていない.

(2) 組

MiniML言語は,OCamlと同じ組を組込みのデータ型として扱うことができる(組についてはObjective Caml入門の3.2節を参照).ただし簡単のため,一つの組に含めることのできる要素の数はちょうど2個であり,また,組から要素を取り出すためのパターンマッチ構文も備えていない.組を扱うための構文は次の2つである.

たとえば,let t = (0, 100) in ...によって生成した組tから第1要素0を取り出すにはt.1と書き,第2要素を取り出すにはt.2と書く(それぞれt.0t.1ではないことに注意).3つ以上のデータをまとめて扱いたい場合には:

let grades = (0, (1, (2, (3, 4)))) in
let good = grades.2.2.2.1

のように,2つ組を必要なだけ入れ子にすれば良い.

(3) ループ構文

関数型言語で何らかの処理の繰り返しを表現する場合,一般的には再帰関数を用いる.MiniML言語でもlet rec式を使って再帰関数を定義することはもちろん可能だが,それ以外に,繰り返しを直接的に表現するための特殊構文も備えている.

loop式は,その見た目からも分かるとおり,let式とほとんど同じ意味を持ち,変数を値に束縛してから本体式の評価を行う.let式と異なるのは,loop式が繰り返しの先頭位置をマークしている点である.loop本体式の中で「recur 」を実行すると,最も内側のloop式の先頭に戻り,の評価結果に束縛し直してから,本体式を再度評価する.ここで,「recur 」は,recurという名前の特別な関数を式に適用しているのではなく,全体で一つの特殊な式であることに注意して欲しい.そのため:

(fun x -> recur) 1

のようなコードは構文エラーであり書くことはできない.

たとえば,からまでの総和を計算するloop式は次のように書くことができる.

loop v = (1, 0) in
if v.1 < 101 then
  recur (v.1 + 1, v.1 + v.2)
else
  v.2;;

recur式には,普通の再帰関数呼出しとは異なり,その評価結果がそれを取り囲む一番内側のloop式全体の評価結果となるような位置にしか書くことができない,という強い制約がある.

ただし,「その評価結果」と書いているが,recur式の評価結果などというものは 存在しないことに注意して欲しい.たとえば,上の総和計算コードを代わりに:

loop v = 1 in
if v < 100 then
  v + recur (v + 1)
else
  v;;

のように書くことは出来ない.

さらに,loop式の本体式中に現れる(fun式やlet rec式による)関数本体式に含まれるrecur式や,同じくloop式の本体式中に現れる「recur 」の式に含まれるrecur式は,外側のloop式と制御構造上の結びつきを持たない.そのため,先程のコードを少し(不自然に)書き換えた:

loop v = (1, 0) in
if v.1 < 101 then
  (fun x -> recur x) (v.1 + 1, v.1 + v.2)
else
  v.2;;

のようなコードは許されない.(当然のことながら,関数本体式の中に別のloop式を書き,その内側にrecur式を含めることは可能である.)

上記の様々な制約を踏まえた上でrecur式を書くことのできるプログラム中の位置のことを,末尾位置 (tail position)と呼ぶ.様々な制約があるため,慣れないうちはきちんと理解するのに時間がかかるかも知れないが,さしあたっては上の例を理解できていれば十分である.末尾位置の正確な定義は,recur式の検査であらためて示す.末尾位置の概念を用いると,上記の制約は「recur式は末尾位置にしか書くことができない」と簡潔に言い換えることができる.

なお,通常の関数についても,関数定義の本体式中に含まれる関数適用式の評価結果が,その本体式全体の評価結果となる場合,その関数適用式は末尾位置にあると言い,その関数適用を末尾呼出し (tail call)と呼んだりする.末尾呼出しを効率良く実装するための最適化手法が数多く存在するが,この実験では時間の都合により扱わないこととする.

BNF文法では上記のrecur構文に関する制約を表現できていないため,入力プログラムが制約を満たしていることの確認は抽象構文木に対して行う.

課題1(任意) 拡張構文
階乗計算を行うMiniMLプログラムを,再帰を用いずループ構文と組を使って書きなさい.同じく,フィボナッチ数を求めるプログラムを,ループ構文と組を使って書きなさい.