type t (* void *)

ソフトウエアのこととか

OCaml: ocamldebugあれこれ

最近ocamldebugとばかり戯れている。Emacsからocamldebugを操作するアレコレを書く。

概略

マニュアルとかmzpさんのブログとかを読んでね。

d.hatena.ne.jp

install_printer

ユーザがプリティプリンタを手書きすることが出来る。でかい構造や型隠蔽されてるデータ構造のデバッグには超重要。 M.t型の値をプリティプリントするには Format.formatter -> M.t -> unit の関数を作り、その.cmoファイルをload_printerで読み込み 関数名を例えばinstall_printer M.printのように指定して読みこめば良い。

cmoファイルは読み込んだ実行形式がロードするとしても、改めてload_printerしないといけないはず。 イチイチ手に書くのが面倒ならばsourceコマンドでファイルを読み込み、ocamldebugのコマンドを実行させることが出来る

例えば'a Hashtbl.t型の値をプリントしたくなることはよくあるかもしれない。が型隠蔽されてる。 その時に以下のようにプリントする関数を作ろう

let print fmt tbl = Hashtbl.iter (fun key value -> Format.fprintf fmt "(%d %d)" key value) tbl

let h = Hashtbl.create 10

let rec loop cnt =
  if cnt = 100 then () else begin
    Hashtbl.add h cnt (succ cnt);
    loop  (succ cnt)
  end

let () = loop 0

この例では(int, int) Hashtbl.t型の値をプリントする関数を作っています。それが上のprint関数です。 では実際に使ってみましょう。6行目、つまりloop関数のif文のところにbreakpointをセットして毎回どういう風に変数hが変わるのかこれで観察できます。

    OCaml Debugger version 4.02.2

(ocd) set program ~/ocaml/a.out
(ocd) break @ Test 6
Breakpoint 2 at 152632: file ./test.ml, line 6, characters 3-95
(ocd) run
Time: 172 - pc: 152632 - module Test
Breakpoint: 2
(ocd) p h
h: (int, int) Hashtbl.t = <abstr>
(ocd) load ../ocaml/test.cmo
File ../ocaml/test.cmo loaded
(ocd) install Test.print
(ocd) p h
h: (int, int) Hashtbl.t = 
(ocd) run
Time: 196 - pc: 152632 - module Test
Breakpoint: 2
(ocd) run
Time: 196 - pc: 152632 - module Test
Breakpoint: 2
(ocd) p h
h: (int, int) Hashtbl.t = (0 1)(1 2)

コマンドを実行する関数を作ろう

Emacsからocamldebugを使っていると、Emacsの関数を通してocamldebugにコマンドを送信することが出来る。 私はこれを多用する。

(defun ocamldebug-run-and-print (arg)
    (interactive "p")
    (loop for i from 1 to arg
        (ocamldebug-call "run")
        (ocamldebug-call "print" "ty ty'")))

もう今回はtype_expr型の値がどう更新されるのか見ていくためだけの関数にするので、変数名直書きする。 これを適当なキーバインドに割り振ってやって、breakpointをセットして実行する。

キーバインドいじる

標準のキーバインドあまり使いやすくないのでいじる。 特にprevious、backコマンドは標準で関数が用意されていないので自分で用意する。

ちなみにocamldebugは後ろに戻れる

ocamldebug.elを読んでみると、各コマンドを実行するだけの関数はdef-ocamldebugを使っているようなので見よう見まねで作ってみる。

(when (require 'ocamldebug nil t)
  (def-ocamldebug "backstep" "\C-b" "Back step one source line with display.")
  (def-ocamldebug "previous" "\C-p" "")
  (define-key ocamldebug-mode-map [(C-up)] 'ocamldebug-up)
  (define-key ocamldebug-mode-map [(C-down)] 'ocamldebug-down)
  (define-key ocamldebug-mode-map [(C-left)] 'ocamldebug-backstep)
  (define-key ocamldebug-mode-map [(C-right)] 'ocamldebug-step)
  (define-key ocamldebug-mode-map [(C-S-left)] 'ocamldebug-previous)
  (define-key ocamldebug-mode-map [(C-S-right)] 'ocamldebug-next))

雑感

ocamldebugには色々機能が足りない。
例えばシグニチャで型隠蔽している型はプリティプリント出来ないし、多相関数の中では渡された変数をプリントすることが出来ない。 多相関数の中でのプリントについては当然で、ocamlは実行時に型情報を基本的に残さないので分かりようがない。 Heapのタグを覗いてもいいかもしれないけどもレジスタにしか確保されない値なんかは手も足も出ない筈だ。 型注釈をユーザが与えてプリント、みたいなことはツールが対応してない。まぁ色々残念だ。

それでもocaml型推論機をデバッグしたりするのには重要なツールだ。 通常Immutableな構造を多用する関数型言語ではそんなデバッガなどはいらないことが多いのだけど、型推論は副作用のカタマリなのでそうもいかない。

真面目にocamldebugを使っている人は殆どいないみたいだけども、 時間があったらocamldebugの改良をやりたい。