プログラムが思った通りに動かないとき,あるいは他の人が書いたプログラムを理解したいときには,プログラムの挙動を調べてみるでしょう。例えば,「入力」に対する「出力」を調べたり,プログラムが保持する「状態」を確認することで,挙動を理解しようとするはずです。そうしたときに使う手法が,プログラム動作中の変数の値といった情報を適宜出力する,いわゆるトレース(trace)です。C言語では情報の表示によくprintfを利用することから,printfデバッグとも呼ばれます。
Haskellでトレースを使うには,二つの問題があります。一つは,Haskellの型は必ずしも値を表示できるものばかりではない点です。これは回避が容易なので,さほど問題ではないでしょう。より深刻なのは「Haskellでは,副作用によるバグの発生を防ぐために,I/Oの扱いに対して制約がある」という点です。この結果,Haskellではトレースが行いにくくなっています。Haskellでは,値を表示するprintのような副作用を持つ処理を,I/Oモナドなどを通して明示的に扱う必要があります。しかし,トレースの対象になるコードがI/Oアクションであるとは限りません。
一体どうすればいいのでしょうか? 今回はこうした疑問に対する答えを提示したいと思います。
型をShowクラスのインスタンスにする
まずは,Haskellで値を表示するにはどうしたらいいかを考えてみましょう。実はヒントは既に紹介しています。この連載の第2回で,printはShowクラスのインスタンスになっている型を表示できると説明しました。
Prelude> :i print print :: (Show a) => a -> IO () -- Defined in System.IO
つまり,型をShowクラスのインスタンスにすることで,その型はprintを使って表示できるようになります。
Showクラスのインスタンスをどのように定義すればいいでしょうか? まず,Showクラスの定義を見てみましょう。Showクラスは,Preludeでは以下のように定義されています(参考リンク)。
type ShowS = String -> String class Show a where showsPrec :: Int -> a -> ShowS show :: a -> String showList :: [a] -> ShowS -- Mimimal complete definition: -- show or showsPrec showsPrec _ x s = show x ++ s show x = showsPrec 0 x "" showList [] = showString "[]" showList (x:xs) = showChar '[' . shows x . showl xs where showl [] = showChar ']' showl (x:xs) = showChar ',' . shows x . showl xs
Showクラスには,showsPrec,show,showListの三つのメソッドがあります。showsPrecはshowを,showはshowsPrecをそれぞれ使って実装されているのがわかります。一方,showListの定義はshowに依存していますが,このメソッド自身は他の二つの定義では利用されていません。したがって,showsPrecかshowのいずれかのメソッドを定義すれば,Showクラスのインスタンスに最低限必要な定義をそろえることができます(参考リンク)。
実際にShowクラスのインスタンスを定義してみましょう。可変長引数を表すことを目的としたVarListという型を定義したとします。
data Variables = C Char | S String | I Int | Iex Integer | D Double | F Float data VarList a = Var a [Variables]
*VarList> :t Var 11 [(Iex 21), (S "fd"), (C 'a')] Var 11 [(Iex 21), (S "fd"), (C 'a')] :: (Num t) => VarList t *VarList> :t Var 11 [] Var 11 [] :: (Num t) => VarList t *VarList> :t Var 11.23 [] Var 11.23 [] :: (Fractional t) => VarList t *VarList> :t Var "fd" [] Var "fd" [] :: VarList [Char]
この型をShowクラスのインスタンスにしてみましょう。Showクラスのインスタンスにするには,先ほど説明したように,showあるいはshowsPrecのどちらかのメソッドを定義すればOKです。showsPrecを定義しようとするとShowクラスに対する細かい説明が必要になるので,ここではshowを定義しましょう。
instance Show Variables where show (C ch) = "C " ++ show ch show (S str) = "S " ++ show str show (I m) = "I " ++ show m show (Iex n) = "Iex " ++ show n show (D o) = "D " ++ show o show (F p) = "F " ++ show p instance Show a => Show (VarList a) where show (Var x y) = "Var " ++ show x ++ " " ++ show y
Variablesが取るそれぞれの型は既にShowクラスのインスタンスとして定義されているので,showメソッドを使ってそれぞれの型を表示させています。VarListのShowクラスのインスタンスを定義する際,型変数aにShow aという文脈を制約として付ける必要があることに注意してください。VarList構成子の第1引数xに対してshowメソッドを使っているため,Showクラスのインスタンスでは型変数aもShowクラスのインスタンスである必要があります。この制限が定義から抜けているとエラーが表示されます。
instance Show (VarList a) where show (Var x y) = "Var " ++ show x ++ " " ++ show y
*VarList> :r [1 of 1] Compiling VarList ( VarList.hs, interpreted ) VarList.hs:18:31: Could not deduce (Show a) from the context (Show (VarList a)) arising from a use of `show' at VarList.hs:18:31-36 Possible fix: add (Show a) to the context of the type signature for `show' In the first argument of `(++)', namely `show x' In the second argument of `(++)', namely `show x ++ " " ++ show y' In the expression: "Var " ++ show x ++ " " ++ show y Failed, modules loaded: none.
では試してみましょう。上で既にShowクラスのインスタンスを作成しているので,内容を直接表示できます。
*VarList> Var 11 [(Iex 21), (S "fd"), (C 'a')] Var 11 [Iex 21,S "fd",C 'a'] *VarList> Var 11 [] Var 11 [] *VarList> Var 11.23 [] Var 11.23 [] *VarList> Var "fd" [] Var "fd" []
ところで,Showクラスのインスタンスをいちいち定義するのは面倒ではないでしょうか? Showクラスのインスタンスは,たいていは定型のコードになります。それなら,いちいち手でコードを書くよりも,自動生成すべきでしょう。
幸いShowクラスは,第5回で紹介した導出インスタンス宣言の対象になっています。データ宣言時であれば,処理系にShowクラスのインスタンスの定義を自動生成させることができます(参考リンク1,参考リンク2)。
data Variables = C Char | S String | I Int | Iex Integer | D Double | F Float deriving Show data VarList a = Var a [Variables] deriving Show
*VarList> :r Ok, modules loaded: VarList. *VarList> Var 11 [(Iex 21), (S "fd"), (C 'a')] Var 11 [Iex 21,S "fd",C 'a'] *VarList> Var 11 [] Var 11 []
また,2007年11月3日にリリースされたGHC6.8.1からは,データ宣言時でなくても使用できるように導出インスタンス宣言を拡張した「独立型導出インスタンス宣言」という機能が提供されています。これを利用すれば「導出インスタンス宣言を使うにはデータ宣言をしたモジュールを変更しなければならない」という制限に悩まされずに済みます。
module VarList where data Variables = C Char | S String | I Int | Iex Integer | D Double | F Float data VarList a = Var a [Variables]
module Standalone where import VarList deriving instance Show (Variables) deriving instance Show a => Show (VarList a)
なお,独立型導出インスタンス宣言を使うには,-X*オプションあるいLANGUAGE指示文を使ってこの拡張機能を有効にする必要があります。実行時に-Xに独立型導出インスタンス宣言を示すStandaloneDerivingという文字列を付けた-XStandaloneDerivingオプションを指定するか,ソースコード上のモジュール宣言より前にヘッダとしてLANGUAGE指示文を追加してください(参考リンク1,参考リンク2)。
$ ghci -XStandaloneDeriving Standalone.hs GHCi, version 6.8.1: http://www.haskell.org/ghc/ :? for help Loading package base ... linking ... done. [1 of 2] Compiling VarList ( VarList.hs, interpreted ) [2 of 2] Compiling Standalone ( Standalone.hs, interpreted ) Ok, modules loaded: VarList, Standalone. *Standalone> Var 11 [(Iex 21), (S "fd"), (C 'a')] Var 11 [Iex 21,S "fd",C 'a'] *Standalone> Var "fd" [] Var "fd" [] *Standalone> Var 11.23 [] Var 11.23 []
{-# LANGUAGE StandaloneDeriving #-} module Standalone where import VarList deriving instance Show (Variables) deriving instance Show a => Show (VarList a)