GHC のDebugger

GWも終わり、静けさを取り戻したと思っていたら ...

祝 FreeBSD 7.2 Release

リリースされましたね。GW中に! この時期のリリースは、日本人への配慮なの だろうか? 日本では、結構隠れ BSDerが居るので、連休中に思いっきり、Update でもして下さいって。GWを逃すと、十分な時間を取れないですから。

おいらは、先ほどcvsupしておいたので、buildworldは今夜決行かな。

祝 Clojure 1.0 Release

After a sustained period of API stability and minimal bug reports, 
I'm happy to announce the release of Clojure 1.0!

http://clojure.googlecode.com/files/clojure_1.0.0.zip

今は、contribの改定がラッシュになっています。落ち着いたら antして、jarファイルを 置き換えておこう。

祝 Debug Hacks

ちょっと前の事だけど、Releaseされた。連休中には、その本の著者様達の居城で ある、ミラクル・リナックスさんを訪問していたのでした。残念ながら時間が無く て、実物を手に入れる事は出来なかったけど、機会が有ったら ...

前職では、ソフトを開発する部門に居たけど、debuggerを使っている人は誰も いなかったなぁ。Bugなんてないから使う場面が無いよと言うのなら、結構な のですが、printf派だったのですかね? 今となっては、知るよしもない。

私は、debugger大好き人間だす。これが無いと安心出来ません。使う場面が 無くとも、使い方を知っておくべき(保険としても)と、思っています。

最近、触り始めた、ghciでも prompt から、:? すれば、debugコマンドも サーと表示されます。が、画面を遡って見るのも面倒なので、見なかった 事にしてたんです。今日は、じっくりと見てみますかね。

ghci の debug command

 -- Commands for debugging:

   :abandon                    at a breakpoint, abandon current computation
   :back                       go back in the history (after :trace)
   :break [<mod>] <l> [<col>]  set a breakpoint at the specified location
   :break <name>               set a breakpoint on the specified function
   :continue                   resume after a breakpoint
   :delete <number>            delete the specified breakpoint
   :delete *                   delete all breakpoints
   :force <expr>               print <expr>, forcing unevaluated parts
   :forward                    go forward in the history (after :back)
   :history [<n>]              after :trace, show the execution history
   :list                       show the source code around current breakpoint
   :list identifier            show the source code for <identifier>
   :list [<module>] <line>     show the source code around line number <line>
   :print [<name> ...]         prints a value without forcing its computation
   :sprint [<name> ...]        simplifed version of :print
   :step                       single-step after stopping at a breakpoint
   :step <expr>                single-step into <expr>
   :steplocal                  single-step within the current top-level binding
   :stepmodule                 single-step restricted to the current module
   :trace                      trace after stopping at a breakpoint
   :trace <expr>               evaluate <expr> with tracing on (see :history)

debuggerと言えば、止めて、見るが基本なので、break print step continueぐら いは、基本中の基本だけど、abandon って、なんやねん?

学が無い私は、辞書が頼りです。最近知ったんですが、携帯にも、国語、英和、和英 が入っていました。オンラインで何処かのサイトから引いてくるのかと思ったら モバイル辞書とか銘打って、内蔵なんですね。便利だなあ!

さて、debugの材料であるが、何にしよう? 丁度、今読んでいる haskellerへの道 があるので、たらい回し を材料にします。

たらい回しの前に

emacs上から、C-c C-l すると、見事にエラーですよ。

Prelude> :load "tak.hs"
[1 of 1] Compiling Main             ( tak.hs, interpreted )

tak.hs:12:24: Not in scope: `intToDigit'

tak.hs:13:55: Not in scope: `intToDigit'

tak.hs:16:46: Not in scope: `digitToInt'
Failed, modules loaded: none.

こりゃ、debugger以前の問題ですな。cut-seaさんは、hugsを使っていて、私は ghciを利用してます。多分、inportが足りないのでしょう。こういうのは、 執事に調べさせましょう。Hayoo!を 呼び出して、これお調べ。

Data.Char.  	 intToDigit  	:: Int -> Char
base 	Convert an Int in the range 0 15 to the corresponding single digit Char This function fails on other... Source 

ありがとさん、import Data.Char しとけばいいのね。ついでに、この関数を使えば 16進数を文字に変換してくれる、なんて事まで分かって、お得です。

Prelude> :load "tak.hs"
[1 of 1] Compiling Main             ( tak.hs, interpreted )
Ok, modules loaded: Main.
*Main> :main 200 100 0
Loading package bytestring-0.9.1.4 ... linking ... done.
Loading package old-locale-1.0.0.1 ... linking ... done.
Loading package old-time-1.0.0.2 ... linking ... done.
Loading package filepath-1.1.0.2 ... linking ... done.
Loading package Win32-2.2.0.0 ... linking ... done.
Loading package directory-1.0.0.3 ... linking ... done.
Loading package process-1.0.1.1 ... linking ... done.
Loading package syb ... linking ... done.
Loading package array-0.2.0.0 ... linking ... done.
Loading package random-1.0.0.1 ... linking ... done.
Loading package haskell98 ... linking ... done.
200
*Main> 

今度は、成功しました。一応、成功した版を再掲します。

import System
import Data.Char

main :: IO ()
main = do args <- getArgs
          putStrLn $ numberToStr $ apply (tak) $ map (strToNumber) args

apply :: (a -> a -> a -> b) -> [a] -> b
apply f [x,y,z] = f x y z

numberToStr :: Int -> String
numberToStr num = if (num < 10)
                  then [intToDigit num]
                  else (numberToStr $ quot num 10) ++ [intToDigit $ rem num 10]

strToNumber :: String -> Int
strToNumber str = foldl1 (\x y->10*x+y) $ map digitToInt str

tak :: Int -> Int -> Int -> Int
tak x y z = if (x > y) then tak (tak (x-1) y z)
                                (tak (y-1) z x)
                                (tak (z-1) x y)
                       else y

いよいよdebuggerだ

さて、どこで止めてみますかね。取りあえず、applyで止めますか。

*Main> :break apply
Breakpoint 0 activated at tak.hs:9:0-24
*Main> :main 6 3 0
Stopped at tak.hs:9:0-24
_result :: b = _
[tak.hs:9:0-24] *Main> :list
8  apply :: (a -> a -> a -> b) -> [a] -> b
9  apply f [x,y,z] = f x y z
   ^^^^^^^^^^^^^^^^^^^^^^^^^
10  
[tak.hs:9:0-24] *Main> :step
Stopped at tak.hs:6:49-70
_result :: [Int] = _
args :: [String] = [['6'],['3'],['0']]
[tak.hs:6:49-70] *Main> :list
5  main = do args <- getArgs
6            putStrLn $ numberToStr $ apply (tak) $ map (strToNumber) args
                                                    ^^^^^^^^^^^^^^^^^^^^^^
7  
[tak.hs:6:49-70] *Main> :step
Stopped at tak.hs:9:18-24
_result :: b = _
f :: a -> a -> a -> b = _
x :: a = _
y :: a = _
z :: a = _
 <emacsで、前コマンドの繰り返しALT-p を何度か実行>
[tak.hs:20:38-40] *Main> :step
Stopped at tak.hs:(20,28)-(22,46)
_result :: Int = _
x :: Int = 5
y :: Int = 3
z :: Int = _

ちょっと例が深遠すぎましたが、計算の過程でどのように、引数が渡って行くかが 見てとれます。 詳細は、こちら が、参考になります。

ここまで書いていて何ですが、Haskellの場合、debuggerは最後の切り札に取って おくのがよさそうです。それより、型システムと言う強力な機構がありますので 関数単位にしっかりと機能チェックするのが、正しいHaskellerへの道だと感じました。