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

仕様としての型クラス: 実装と仕様をわける

(工事中 70%)

はじめに

型クラスは型の「性質」だ。性質は特定のクラス関数を持つということで表現される。クラス関数はその型を扱うためのAPIとも見ることができる。型クラスは「仕様」と考えられる。

倉庫

intStorage.hs

整数を格納する倉庫を考えよう。整数を格納する、整数を取り出すの2つの仕事ができる。どういう順で取り出すかは倉庫の種類によってまちまちだ。また、同じ整数が2回格納されたときにそれを2つとして扱うか、1つとして扱うかも倉庫の種類による。

型クラスIntStorage

型クラスIntStorageを以下のように定義する。

class IntStorage s where
empty :: s
store :: Int -> s -> s
derive :: s -> Maybe (Int, s)

空の倉庫として値emptyが定義される。整数をたくわえる関数storeと整数をとりだす関数deriveとが定義されればそれを整数の倉庫と考えることができる。

これらのクラス関数は「倉庫」として使える型の持つべきインターフェース、「仕様」と考えることができる。

仕様のうえで

倉庫の実装を考えるまえに型クラスIntStorageによってあたえられた仕様のうえでコードを書いてみよう。

値をたくわえる

適当に値をたくわえた倉庫をつくる。

stored :: IntStorage s => s
stored = let
s1 = store 8 empty
s2 = store 5 s1
s3 = store 2 s2
s4 = store 10 s3
s5 = store 3 s4 in
s5

空の倉庫に8, 5, 2, 10, 3と5個の整数をたくわえた。順にたくわえていっている感じを出すためにあえてletを使った変数への束縛を行った。

値をとりだす

倉庫から値を3つとりだしてみる。これもわかりやすさのためにあえてどろくさい書きかたとする。

derive3 :: IntStorage s => s -> Maybe [Int]
derive3 s = case derive s of
Just (l, s1) -> case derive s1 of
Just (m, s2) -> case derive s2 of
Just (n, s3) -> Just [l, m, n]
_ -> Nothing
_ -> Nothing
_ -> Nothing

関数deriveは値をとりだせればJust値をかえす。Just値ならばさらに次の値をとりだす。同じことをもう1回行い最後にとりだした値l, m, nをリストにして返す。

実際の倉庫

単純なリスト

まずは整数のリストを整数の倉庫として使う例だ。普通に考えると以下のようなインスタンス宣言になりそうだ。

instance IntStorage [Int] where
...

これはHaskellでは許されない。以下のような書きかたとの整合性がとれないからだ。

instance IntStorage [a] where
...

このようなときはnewtypeによって型をラップする。

newtype ListInt = LI [Int] deriving Show

instance IntStorage ListInt where
empty = LI []
store n (LI s) = LI $ n : s
derive (LI (n : s)) = Just (n, LI s)
derive _ = Nothing

試してみよう。

% ghci intStorage.hs
*Main> stored :: ListInt
LI [3,10,2,5,8]
*Main> derive3 it
Just [3,10,2]

ソートされた集合

標準ライブラリにあるSetを使ってみよう。これは集合としての演算を効率的に行えるデータ構造であり、内部的に値はソートして格納される。

import qualified Data.Set as S

newtype SetInt = SI (S.Set Int) deriving Show

instance IntStorage SetInt where
empty = SI S.empty
store n (SI s) = SI $ S.insert n s
derive (SI s)
| S.null s = Nothing
| otherwise = Just (n, SI s')
where (n, s') = S.deleteFindMin s

試してみよう。

*Main> :reload
*Main> stored :: SetInt
SI (fromList [2,3,5,8,10])
*Main> derive3 it
Just [2,3,5]

リストによるキュー

リストのペアを使うことで簡易的なキューを作ることができる。

data QueueInt = QI [Int] [Int] deriving Show

instance IntStorage QueueInt where
empty = QI [] []
store n (QI f r) = QI f (n : r)
derive (QI (n : f) r) = Just (n, QI f r)
derive (QI _ r@(_ : _)) = derive $ QI (reverse r) []
derive _ = Nothing

試してみよう。

*Main> :reload
*Main> stored :: QueueInt
QI [] [3,10,2,5,8]
*Main> derive3 it
Just [8,5,2]

奇数をさきに

今度は奇数からさきにとりだされる倉庫を考えてみよう。

data OddEven = OE [Int] [Int] deriving Show

instance IntStorage OddEven where
empty = OE [] []
store n (OE os es)
| odd n = OE (n : os) es
| otherwise = OE os (n : es)
derive (OE (o : os) es) = Just (o, OE os es)
derive (OE _ (e : es)) = Just (e, OE [] es)
derive _ = Nothing

試してみる。

*Main> :reload
*Main> stored :: OddEven
OE [3,5] [10,2,8]
*Main> derive3 it
Just [3,5,10]

まとめ

仕様としての型クラスについて見てきた。クラス関数によってインターフェースが示される。インスタンス宣言によって実装が示される。型を変えることで実装を選ぶことができる。

「構文: newtype」へもどる 「クラス関数のデフォルト定義」へ

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