最近ocamldebugとばかり戯れている。Emacsからocamldebugを操作するアレコレを書く。
概略
マニュアルとかmzpさんのブログとかを読んでね。
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の改良をやりたい。
ocamldebugの何が悲しいって型隠蔽してる型の値プリント出来ないし多相関数の中に入ると<poly>って表示されるだけでプリント出来ないし、これはショウガナイかなと思うのだけどユーザが型を書いてプリント、みたいなことも出来ないことですね
— Ocamlアイドル (@no_maddo) 2015, 7月 31
@no_maddo 大きいとこは ocamldebug 使わないからねぇ。デバグの為にバイトコード版時間かけて作ってもネイティブと同じようにコケるとは限らないし…
— らくだの卯之助 (@camloeba) 2015, 7月 31
@no_maddo async とか使うともはやあまり役に立たないという感じでしたね…
— らくだの卯之助 (@camloeba) 2015, 7月 31
@no_maddo むろん ocamlc 改造時のデバグとかには有効だと思います。(けど私なら単にprintfデバッグするわ
— らくだの卯之助 (@camloeba) 2015, 7月 31