top page > computer > haskell > web_lecture > for_programmer > typeclass_show.html
更新日:
文責: 重城良国

型クラス: Show

(工事中 70%)

表示可能

型クラスShowは「対話環境などで表示可能」という性質を表す。クラス関数にはshow, showsPrec, showListがある。最低限必要な定義は関数showまたは関数showsPrecだ。

関数show

show :: a -> String

これは簡単だ。表示したい値から文字列を返す関数を定義してやればいい。

ドリンクのサイズ

drinkSize.hs

instance Show Size where
show Short = "Short"
show Tall = "Tall"
show Grande = "Grande"
show Venti = "Venti"

% ghci drinkSize.hs
*Main> Short
Short
*Main> Grande
Grande

関数showsPrec

type ShowS = String -> String
showsPrec :: Int -> a -> ShowS

関数showsPrecは関数showに2つの要素が追加されている。

だ。

差分リストによる効率化

leftList.hs

関数showと関数showsPrecのひとつめの違いとして「差分リストによる効率化」を見る。showsPrecはshowに`sPrec'がついた形だがこの`s'の部分だ。通常とは左右をひっくりかえした形になっている左リストを考えてみよう。

data LL a = Nil | Cons (LL a) a

関数showで定義

これを「表示可能」にするのだが、まずはクラス関数showのほうで定義してみよう。

instance Show a => Show (LL a) where
show (Cons ll x) = "Cons (" ++ show ll ++ ") " ++ show x
show _ = "Nil"

試してみよう。

% ghci leftList.hs
*Main> Cons (Cons (Cons Nil 8) 5) 3
Cons (Cons (Cons (Nil) 8) 5) 3

右結合に

関数showを使った定義ではこのリストが大きくなったときに効率の低下が起きる。show (Cons ll x) = "Cons (" ++ show ll ++ ") " ++ show xの行のshow llは左結合でどんどん深くなっていく。効率が悪い。差分リストを利用することで効率を改善しよう。

instance Show a => Show (LL a) where
showsPrec _ (Cons ll x) =
("Cons (" ++) . showsPrec 11 ll . (") " ++) . showsPrec 11 x
showsPrec _ _ = ("Nil" ++)

数字の11は今のところ無視してほしい。(++)でつなぐかわりに差分リストを(.)で結合している。これによって文字列の結合自体はつねに右結合となる。

値構築演算子の優先順位の処理

showBinding.hs

以下のようなデータ型がある。

data A = B :+: B

data B = Int :+: Int

infixl 6 :+:
infixl 7 :*:

これらを演算子の結合力を考慮して不要な括弧なしで表示したい。以下のようにすれば良い。

instance Show A where
showsPrec d (b :+: c)
| d > 6 = ("(" ++)
. showsPrec 7 b . (" :+: " ++) . showsPrec 7 c
. (")" ++)
| otherwise = showsPrec 7 b . (" :+: " ++) . showsPrec 7 c

instance Show B where
showsPrec d (m :*: n)
| d > 7 = ("(" ++)
. showsPrec 8 m . (" :*: " ++) . showsPrec 8 n
. (")" ++)
| otherwise = showsPrec 8 m . (" :*: " ++) . showsPrec 8 n

showsPrecの第1引数dは「今いる環境の結合力」を示している。dが問題としている演算子の結合力よりも大きければ括弧が必要となり、そうでなければ括弧は不要だ。内側の値に対するshowsPrecは問題としている演算子の結合力に1足したものを変数dとして与える。

関数showParen

showsPrecを書きやすくするための関数showParenが定義されている。これを使うとより簡単に書くことができる。

instance Show A where
showsPrec d (b :+: c) = showParen (d > 6) $
showsPrec 7 b . (" :+: " ++) . showsPrec 7 c

instance Show B where
showsPrec d (m :*: n) = showParen (d > 7) $
showsPrec 8 m . (" :+: " ++) . showsPrec 8 n

関数showParenは第1引数にBool値をとりその値によって続くShowS型の値の前後に丸括弧をつけるかどうかを決める。

関数shows

また、関数showと関数showsPrecの中間的な関数として関数showsもある。これはクラス関数ではないので再定義はできないが以下のように定義されている。

shows = showsPrec 0

関数showList

showList.hs

クラス関数showListはほぼStringの表示のために存在する関数だ。このトリックによってString(文字のリスト)は

['H', 'e', 'l', 'l', 'o']

ではなく

"Hello"

と表示される。まずリストの表示の定義を見てみよう。

instance Show a => Show [a] where
showsPrec _ = showList

クラス関数showsPrecが関数showListによって定義されている。このshowListはa型の値に対するshowListである。クラス関数showListにはデフォルト定義があり、以下のように定義されている。

showList [] = ("[]" ++)
showList (x : xs) = ("[" ++) . shows x . sl xs
where
sl [] = ("]" ++)
sl (y : ys) = ("," ++) . shows y . sl ys

たとえば[1, 2, 3]であれば"[1,2,3]"のように表示するような定義である。つまり、デフォルトの定義のままであればこのように「リストっぽく」表示する。このクラス関数showListはCharに対してはデフォルト定義ではなく「文字列っぽく」表示されるように上書きされている。よってaがCharの場合には関数showSPrecつまりshowListは[a]について「文字列っぼい」表示となる。

まとめ

クラスShowは

という事情によっていろいろと複雑になっている。本質的にはクラス関数showが理解できればいいが、これらのための工夫をひとつひとつ解き明かしていく作業も楽しく勉強になる。

「アルゴリズム: 差分リスト」へもどる 「型クラス: Bounded」へ

正当なCSSです! HTML5 Powered with CSS3 / styling, and Semantics