C言語風のループ
OCamlの標準のforループは指定回数だけループする機能しかない。ループは再帰で書けということだと思うけど、そういう再帰関数の中には副作用を持つ操作をなるべく含めたくなし、breakするにも書きにくい。
そこで、C言語風のforループを書ける構文拡張を作ってみた。
構文は
for (変数名 = 初期値; 条件式; 次の値) do 処理 done
となっている。ただし、再帰関数を使ったコードを作るので”次の値”はループ変数を更新するのではなくあくまで次の値なので注意。 また、ループのヘッダのところを省略すると、それぞれ
for ( () = (); true; () )
と書いたことになる。( for ( ;; ) と書くと ";;"がひとつのトークンになるので間にスペースを入れる )
break動作は breakと書く。continueはない(末尾再帰にならなくなるので)けどなくても大丈夫だろう。
また、whileループや for .. (to/dowonto) do .. done のループでもbreakが使えるようにしてある。
例:
#load "pa_cloop.cmo";; # for (l = [1;2;3]; l <> []; List.tl l) do Printf.printf "%d\n" (List.hd l) done;; 1 2 3 - : unit = () # for (i = 0; i < 3; i + 1) do Printf.printf "%d\n" i done;; 0 1 2 - : unit = () # let i = ref 0;; val i : int ref = {contents = 0} # for (; !i < 3; incr i) do Printf.printf "%d\n" !i done;; 0 1 2 - : unit = () # for (i = ref 0; !i < 10; i) do if !i mod 2 = 0 then i := !i + 1 else i := !i + 3; Printf.printf "%d\n" !i done;; 1 4 5 8 9 12 - : unit = () # for (i = 0; ; i + 1) do if i = 3 then break else (); Printf.printf "%d\n" i done;; 0 1 2 - : unit = () #
ソース
(*pp camlp4o pa_extend.cmo q_MLast.cmo *) open Pcaml let gensym = let cnt = ref 0 in fun var -> let x = incr cnt; !cnt in var ^ "_thgensymth" ^ string_of_int x (* 同じ例外を投げると恐ろしいバグになるのでかぶらない文字列にしておく *) let break_str = ",1%#'$]1!zvks='9|)o" let gen_for _loc v iv wh nx e = let loop_fun = gensym "iter" in <:expr< try let rec $lid:loop_fun$ $v$ = if $wh$ then do { $e$; $lid:loop_fun$ $nx$ } else () in $lid:loop_fun$ $iv$ with [Sys_error $str:break_str$ -> ()] >> let gen_while _loc b e = <:expr< try while $b$ do { $e$ } with [Sys_error $str:break_str$ -> ()] >> let gen_native_for _loc v iv df e1 e2 = <:expr< try for $v$ = $iv$ $to:df$ $e1$ do { $e2$ } with [Sys_error $str:break_str$ -> ()] >> DELETE_RULE expr: "while"; SELF; "do"; SELF; "done" END EXTEND GLOBAL: expr; expr: LEVEL "expr1" [ [ "for"; "("; init = init; ";"; wh = cond ; ";"; nx = next; ")"; "do"; e = expr; "done" -> gen_for _loc (fst init) (snd init) wh nx e | "while"; b = expr; "do"; e = expr; "done" -> gen_while _loc b e | "for"; v = LIDENT; "="; iv = expr; df = direction_flag; e1 = expr; "do"; e2 = expr; "done" -> gen_native_for _loc v iv df e1 e2 | "break" -> <:expr<ignore(raise (Sys_error $str:break_str$))>> ] ]; direction_flag: [ [ "to" -> true | "downto" -> false ] ]; init: [ [ v = patt LEVEL "simple"; "="; iv = expr LEVEL "expr1" -> (v, iv) | -> (<:patt<()>>, <:expr<()>>) ] ]; cond: [ [ e = expr LEVEL "expr1" -> e | -> <:expr<True>> ] ]; next: [ [ e = expr LEVEL "expr1" -> e | -> <:expr<()>> ] ]; END