Previous Up Next

3.5��ML3 — 関数の導入

ここまでのところ,この言語には,いくつかのプリミティブ関数(二項演算子) しか提供されておらず,MLプログラマが(プリミティブを組み合わ せて)新しい関数を定義することはできなかった.ML3 では,fun式 による関数抽象と,関数適用を提供する.

3.5.1��関数式と適用式

まずは,ML3 の式の文法を示す.

<式>::=…�
 |fun�<識別子>�→�<式>�
 |<1><2>

構文に関するインタプリタ・プログラムは,図8に示す. 適用式は左結合で,他の全ての演算子よりも結合が強いとする.


syntax.ml:

type exp = 
   ...
  | FunExp of id * exp
  | AppExp of exp * exp

parser.mly:

%token RARROW FUN

Expr :
   ...
   | FunExpr { $1 }

MExpr :
    MExpr MULT AppExpr { BinOp (Mult, $1, $3) }
  | AppExpr { $1 }

AppExpr :
    AppExpr AExpr { AppExp ($1, $2) }
  | AExpr { $1 }

lexer.mll:

let reservedWords = [
   ...
  ("fun", Parser.FUN);
   ...
]
...
| "=" { Parser.EQ }
| "->" { Parser.RARROW }

Figure 8: 関数と適用(1)


3.5.2��関数閉包と適用式の評価

さて,Objective Camlと同様,ML3 においても,関数は式を評価した結果とな るだけでなく,変数の束縛対象にもなる第一級の値(first-class value)として扱う.そのため,expressed value, denoted value ともに 関数値を加えて拡張する.

Expressed Value=整数�(…,�−2,�−1,�0,�1,�2,�3,�…)�+�真偽値�+ 関数値�
Denoted Value=Expressed Value

さて,関数値をどのように表現するかを考える.fun 式が適用された時には,パラメータを実引数(の値)に束縛し,関数本体を評価 するので,少なくともパラメータの名前と,本体の式の情報が必要である.しかし, 一般的には,以下の例のように,関数本体の式にパラメータ以外の変数(つま り自由変数)が出現することも可能である.


let x = 2 in
let addx = fun y  x + y in
addx 4

x の静的束縛を実現するためには,この addx の適用を行う際には 本体中の x2 であることを記録しておかなければならない. というわけで,一般的に関数が適用される時には,

  1. パラメータ名
  2. 関数本体の式,に加え
  3. 本体中のパラメータで束縛されていない変数(自由変数)の束縛情報 (名前と値)

が必要になる.この3つを組にしたデータを関数閉包・クロージャ(function closure) と呼び,これを関数値として用いる.ここで作成するインタプリ タでは,本体中の自由変数の束縛情報として,fun式が評価された時点 での環境全体を使用する.これは,本体中に現れない変数に関する余計な束縛 情報を含んでいるが,もっとも単純な関数閉包の実現方法である.

以上を踏まえた eval.ml への主な変更点は図9のようになる. 式の値には,環境を含むデータである関数閉包が含まれるため,exvaldnval の定義が(相互)再帰的になる.関数 値は ProcV コンストラクタで表され,上で述べたように,パラメータ名の リスト,本体の式と環境の組を保持する.eval_expFunExp を処理す る時には,その時点での環境,つまりenv を使って関数閉包を作っている. 適用式の処理は,適用される関数の評価,実引数の評価を行った後,本当に適 用されている式が関数かどうかのチェックをして,本体の評価を行っている. 本体の評価を行う際の環境 newenv は,関数閉包に格納されている環境を,パ ラメータ・実引数で拡張して得ている.


eval.ml:

type exval =
    IntV of int
  | BoolV of bool
  | ProcV of id * exp * dnval Environment.t
and dnval = exval

let rec eval_exp env = function
   ...
  | FunExp (id, exp) -> ProcV (id, exp, env)
  | AppExp (exp1, exp2) ->
      let funval = eval_exp env exp1 in
      let arg = eval_exp env exp2 in
       (match funval with
            ProcV (id, body, env') ->
              let newenv = Environment.extend id arg env' in
                eval_exp newenv body
          | _ -> err ("Non-function value is applied"))

Figure 9: 関数と適用(3)


Exercise�8��[必修課題] ML3 インタプリタを作成し,高階関数が正しく動作するかなどを含めて テストせよ.
Exercise�9��[難易度 2] Objective Caml での「(中置演算子) 」記法をサポートし,プリミティブ演算 を通常の関数と同様に扱えるようにせよ.例えば

let threetimes = fun f  fun x  f (f x x) (f x x) in
  threetimes (+) 5
は,20を出力する.
Exercise�10��[難易度 1] Objective Caml の
fun x1 ... xn → ...
let f x1 ... xn = ...
といった簡略記法をサポートせよ.
Exercise�11��[難易度 1] 以下は,加算を繰り返して 4 による掛け算を実現している ML3 プログラムである.これを改造して,階乗を計算するプログラムを 書け.

let makemult = fun maker  fun x 
                 if x < 1 then 0 else 4 + maker maker (x + -1) in
let times4 = fun x  makemult makemult x in
  times4 3
Exercise�12��[難易度 1] 静的束縛とは対照的な概念として動的束縛(dynamic binding)があ る.動的束縛の下では,関数本体は,fun式を評価した時点ではなく, 関数呼び出しがあった時点での環境をパラメータ・実引数で拡張した環境下 で評価される.例えば,

let a = 3 in
let p = fun x  x + a in
let a = 5 in
  a * p 2
というプログラムで,関数p本体中の a3 ではなく 5 に束縛される.インタプリタを改造し,動的束縛を 実現せよ.
Exercise�13��[難易度 1] 動的束縛の下では,ML4 で導入するような再帰定義を実現するための特別な仕組みや,Exercise�11のようなトリックを使うことなく, 再帰関数を定義できる.以下のプログラムを静的束縛・動的束縛の下で 実行し,結果について説明せよ.

let fact = fun n  n + 1 in
let fact = fun n  if n < 1 then 1 else n * fact (n + -1) in
  fact 5

Previous Up Next