OCaml で遅延（怠惰*1）評価を 実装する方法があるが， 毎回 lazy や Lazy.force を書かないといけないため非常に面倒である． たとえば，ML の授業などでは，

type 'a lazy_list = Nil | Cons of 'a * 'a lazy_list lazy_t

1::2::3::...

Cons(1, lazy(Cons(2, lazy(Cons(3,lazy ...)))))

lazy

let cons x xs = Cons ( x, lazy xs )

cons

xs

lazy

if

if b then t else e

b

t

e

let cond b t e = if b then t else e

cond

b

t

e

とすれば簡単，と教わるのではないだろうか． このとき，のようなリストを定義するためにはと記述する必要があり，を毎回書くのが嫌になるかもしれない．だからといってなどとしてはいけない． OCaml では，ユーザの定義する全ての関数は正格関数となるため，が呼ばれた時点でが必ず評価されてしまい， それを後からで遅延しても全く意味がない． また，文を再定義する際にも同様のことが言える． OCaml では，式は，の真偽値が確定した時点でを評価するように 非正格に定義されているが，これをなどと再定義してしまっては意味がない． なぜなら，が呼ばれた時点で，の真偽値に関係なくも評価されてしまうからだ． そこで，この記事では camlp4 を利用した（似非）遅延評価の実装方法を紹介する． なお，ここでは camlp4-3.09.2 を用いるものとする．

まず，下に示すプログラム pa_lazy.ml を用意する． 簡単に言うと，このプログラムは次のような変換 T[ _ ] をしているだけである．

T[ x ] = Lazy.force x

T[λx.M ] = λx.T[ M ]

T[ M N ] = T[ M ] (lazy T[ N ])

open Pcaml let _loc = Token .dummy_loc let lident_patt = Grammar . Entry .create Pcaml .gram "lident_patt" and simple_expr = Grammar . Entry .create Pcaml .gram "simple_expr" let _ = let rec fun_expr ps e = match ps with [] -> e | p :: ps -> < :expr < fun [ $p$ -> $fun_expr ps e$ ] >> in let rec eta_lazy n ps e = let v = "x" ^ string_of_int n in if n <= 0 then fun_expr ( List .rev ps ) e else eta_lazy ( n- 1 ) (< :patt < $lid:v$ >>:: ps ) (< :expr < $e$ ( Lazy .force $lid:v$ )>>) in DELETE_RULE expr: SELF ; SELF END ; DELETE_RULE patt: SELF ; SELF END ; DELETE_RULE expr: SELF ; "::" ; SELF END ; DELETE_RULE patt: SELF ; "::" ; SELF END ; DELETE_RULE expr: "[" ; "]" END ; DELETE_RULE patt: "[" ; "]" END ; EXTEND str_item: LEVEL "top" [[ "let" ; "lazy" ; p = LIDENT ; "=" ; e = simple_expr ; "[" ; i = INT ; "]" -> < :str_item < declare value $lid:p$ = lazy $eta_lazy ( int_of_string i ) [] e$ ; end >> ]]; simple_expr: [[ v = LIDENT -> < :expr < $lid:v$ >> | e = expr LEVEL "simple" -> e ]]; expr: LEVEL "simple" [[ v = LIDENT -> < :expr < Lazy .force $lid:v$ >> | "[" ; "]" -> < :expr < `nil >> ]]; expr: LEVEL "apply" [ LEFTA [ e1 = expr ; e2 = expr -> < :expr < $e1$ (lazy $e2$ ) >> ]]; expr: AFTER "^" [ RIGHTA [ hd = SELF ; "::" ; tl = SELF -> < :expr < `cons (lazy $hd$, lazy $tl$ ) >> ]]; patt: [ RIGHTA [ hd = lident_patt ; "::" ; tl = lident_patt -> < :patt < `cons ( $hd$,$tl$ ) >> ]]; patt: LEVEL "simple" [[ "[" ; "]" -> < :patt < `nil >> ]]; lident_patt: [[ v = LIDENT -> < :patt < $lid:v$ >> | "_" -> < :patt < _ >> ]]; let_binding: [[ v = LIDENT ; ps = LIST0 patt LEVEL "simple" ; "=" ; e = expr -> (< :patt < $lid:v$ >> , < :expr < lazy $fun_expr ps e$ >>) | "_" ; "=" ; e = expr -> (< :patt < _ >> , e ) ]]; END

ocamlc -pp "camlp4o pa_extend.cmo q_MLast.cmo" -I +camlp4 -c pa_lazy.ml

既存の関数は let lazy （新しい関数名） = （元の関数名）[（引数の数）] で再定義．

で再定義． 中置演算子は再定義不可．

部分適用は禁止．

リストのコンストラクタは， :: と [] のみ． [1;2] などは不可．

と のみ． などは不可． 関数定義の際の引数やリストパターンの引数は変数のみ．

式 let _ = ... の評価は遅延されない．

の評価は遅延されない． （その他にもいろいろあるかもしれないが省略）

if

cond

let lazy print_int = print_int [ 1 ] let lazy print_char = print_char [ 1 ] let lazy succ = succ [ 1 ] let lazy pred = pred [ 1 ] let lazy gt = (>)[ 2 ] let lazy md = (mod)[ 2 ] let cond b t e = if b then t else e let rec from n = n :: from ( succ n ) let rec sieve p l = match l with x :: xs -> cond ( gt ( md x p ) 0 ) ( x :: sieve p xs ) ( sieve p xs ) let rec primes l = match l with x :: xs -> x :: primes ( sieve x xs ) let rec print_n n l = match l with x :: xs -> cond ( gt n 0 ) ( print_int x ; print_char ' ' ; print_n ( pred n ) xs ) () let _ = print_n 100 ( primes ( from 2 ))

ocamlc -o primes.bin -pp "camlp4o ./pa_lazy.cmo" primes.ml

primes.bin

つまり，その値が参照されない限り関数の引数の評価を行わないようなプログラムに翻訳される． その他，リストのコンスト ラク タなども遅延リスト用に変換している．これを，などとして コンパイル する． これを利用することにより，以下の制限のもと全ての値の評価が遅延されるような OCaml のプログラムが導出できる （手抜きなので余計な制限が多いが気にしないように）．例えば，一見 OCaml では止まらなさそうな以下の 素数 生成プログラム primes.ml も停止する． なお，非正格になっているかどうかの検証のため， 敢えての代わりに上記のを使ってみた． コンパイル 方法は以下の通り．実際に，を実行すると 100 個の 素数 が出力される．