01

12

[Haskell] 純粋な関数の中でprintfデバッグをする

2013.01.12(23:24)

できないと思ってつぶやいていたら、@kazu_yamamoto さんに教えていただきました。

Haskellでのデバッグ ~ あどけない話
http://d.hatena.ne.jp/kazu-yamamoto/20120606/1338957783

純粋な関数の中で
putStrLn :: String -> IO ()
のような不純な関数を呼び出すことはできないが、代わりに
trace関数を呼び出してトレース結果を表示させることができる

{-# OPTIONS -Wall -Werror #-}

module TraceTest where

-- | traceを使うには、Debug.Traceをimportする。
import Debug.Trace

-- | traceする変数は、showできるものでなければならない。
-- | traceしたい変数が showできるものでなかったら、
-- | まず、Show型クラスのインスタンスにしておく。

-- | traceの型は以下のとおり
-- | trace :: String -> a -> a
-- | 第1引数がtraceする文字列、第2引数をそのままtraceが出力する。
-- | つまり、aを使っている場所を、trace 文字列 a に置き換えれば、
-- | traceが aを返してくれるので traceを入れる前と同じ処理にできる。

-----------------------------------------------------------------------
-- | サンプルの関数1
-- | traceを入れる前
{--
id' :: a->a
id' x = x
--}
-- | traceを入れた後
-- | このままではaがShow型ではないのでaに型制約を追加すると、
-- | 引数をshowできて、traceで出力できる。
id' :: (Show a)=>a->a
id' x = trace ("id "++show x) x

-----------------------------------------------------------------------
-- | サンプルの関数2
-- | 左畳込みは、リストの左(先頭)から順に関数に渡される
-- | foldl :: (a->b->a)->a    ->[b]     -> a
-- |          2引数関数   初期値  畳込み対象リスト
-- | traceを入れる前
{--
sum' :: (Num a)=>[a]->a
sum' = foldl (\acc x -> acc+x) 0
--}
-- | traceを入れた後
sum' :: (Num a,Show a)=>[a]->a
sum' xs = trace (show result) result
          where result = foldl (\acc x -> (trace ("(acc,x)=" ++ show (acc,x)) (acc+x))) 0 xs

-----------------------------------------------------------------------
-- | サンプルの関数3
-- | 右畳込み
-- | foldr :: (a->b->b)->b  -> [a]    -> b
-- |          2引数関数   初期値 畳込み対象リスト
-- | traceを入れる前
{--
append :: [a] -> [a] -> [a]
append xs ys = foldr (:) ys xs
--}
-- | traceを入れた後
-- | (:)では引数が見えないのでそれと同じことをする、引数がある関数を用意。
{--
append xs ys = foldr (\z zs->z:zs) ys xs
--}
append :: (Show a)=>[a] -> [a] -> [a]
append xs ys = trace (show result) result
       where result = foldr (\z zs->(trace ("(z,zs)=" ++ show (z,zs)) (z:zs))) ys xs

-----------------------------------------------------------------------
-- | サンプルの関数4
-- | 以下の例では showできることがわかっているIntをtraceしている。
-- | Int->Intであるlog2を実装する
log2,log2' :: Int->Int

-- | リスト内包表記の中で、述語を満たす要素を順番に探すところをtrace
-- | traceを入れる前
{--
log2 c
    | c < 1     = error "log input error"
    | otherwise = head [b | (a,b)<-zip (iterate (*2) 1) [-1..], c<a] 
--}
-- | traceを入れた後
log2 c
    | c < 1     = error "log input error"
    | otherwise = head [b | (a,b)<-zip (iterate (*2) 1) [-1..], trace ("(c,a,b) = " ++ show (c,a,b)) (c<a)] 

-- | 蓄積変数を使って、関数を再帰呼び出しているところをtrace
-- | traceを入れる前
{--
log2' c
  | c < 1     = error "log input error"
  | otherwise = log2x c 0
        where log2x e f
                 | e < 2     = f
                 | otherwise = log2x (div e 2) (succ f)
--}
-- | traceを入れた後
log2' c
  | c < 1     = error "log input error"
  | otherwise = log2x c 0
        where log2x e f
                 | e < 2     = trace ("(e,f)=" ++ show (e,f) ++ " done") f
                 | otherwise = trace ("(e,f)=" ++ show (e,f)) (log2x (div e 2) (succ f))

-----------------------------------------------------------------------
-- | サンプルの関数5
-- | Tree型の定義。Tree全体がShowできることにする。
data Tree a = Empty | Node a (Tree a) (Tree a)
              deriving Show

-- | Treeの高さを求める
-- | traceを入れる前
{--
getHeight :: Tree a -> Int
getHeight Empty = 0
getHeight (Node _ left right)
     = max (getHeight left) (getHeight right) + 1
--}
-- | traceを入れた後
getHeight :: (Show a)=>Tree a -> Int
getHeight Empty = 0
getHeight (Node n left right)
    = trace ("(n,left,right,height)=" ++ show (n,left,right,height)) height
      where height = max (getHeight left) (getHeight right) + 1

-- | サンプルのTree
tree1 :: Tree String
tree1 = Node "Node5" (Node "Node1" Empty Empty) (Node "Node7" Empty Empty)

-----------------------------------------------------------------------
-- | main
main :: IO ()
main = do
  print $ id' (8::Int)
  print $ sum' ([1,2,3]::[Int])
  print $ append "abc" "def"
  print $ log2 10
  print $ getHeight tree1
  return ()



実行結果は
id 8
(acc,x)=(0,1)
(acc,x)=(1,2)
(acc,x)=(3,3)
6
(z,zs)=('c',"def")
(z,zs)=('b',"cdef")
(z,zs)=('a',"bcdef")
"abcdef"
(c,a,b) = (10,1,-1)
(c,a,b) = (10,2,0)
(c,a,b) = (10,4,1)
(c,a,b) = (10,8,2)
(c,a,b) = (10,16,3)
(n,left,right,height)=("Node1",Empty,Empty,1)
(n,left,right,height)=("Node7",Empty,Empty,1)
(n,left,right,height)=("Node5",Node "Node1" Empty Empty,Node "Node7" Empty Empty,2)
8
6
"abcdef"
3
2
この実行結果から、
putStrLnやprint などの標準出力と別にトレース結果が出てくる、
otherwiseのほか、リスト内包表記の述語など、どこでも使えるらしい、
ということがわかる。
プロフィール

島敏博

Shima Toshihiro 島敏博
信州アルプスハイランド在住。HaskellとElixirが好き。組み込みソフトウェアアーキテクト、C++プログラマ、山歩き、美術館巡り、和食食べ歩き、日本赤十字社救急法指導員、インデックス投資、クラシック音楽、SESSAME会員、状態マシン設計、モデル駆動開発、ソフトウェアプロダクトライン、Rubyist、実践ビジネス英語

■ ツイッター
http://twitter.com/saltheads
■ Facebook
http://www.facebook.com/saltheads
■ Qiita
http://qiita.com/saltheads

印刷する場合は、ブラウザの印刷メニューではなく、このページの上から3cmくらいの青いところにある、「印刷」を押してみてください。少しうまく印刷できます。まだ完全ではないのですが、これで勘弁してください。


カテゴリ
最新記事
月別アーカイブ
最新コメント
検索フォーム
リンク
sessame
RSSリンクの表示