人気ブログランキング | 話題のタグを見る

型クラス制約 Type Class Constraint

前回紹介したヒストグラムを作るためのモジュール Histogram.hs は [Int] 型のリストを扱うものだが、[Double] 型のリストを使って実数も扱えるようにしてみた。ヒストグラムの計算など [Int] 型のリストと [Double] 型のリストに共通する操作を histogram と histogramR のように別々の関数にせず、同じ histogram という関数にしたかったので色々と調べてみたが、結局、型クラス制約 (type class constraint) を使うのが良いというのがわかった。

型クラス制約とは何かということを説明するより、実際のソースをみた方が分かりやすいので、実数の計算ができるようになった Histogram.hs を次に示す。

module Histogram where

toint :: [String] -> [Int]
toint = map (read :: String->Int) . (filter (/=""))

toreal :: [String] -> [Double]
toreal = map (read :: String->Double) . (filter (/=""))

fromFile :: String -> IO [Int]
fromFile fname = readFile fname >>= return . toint . lines

fromFileR :: String -> IO [Double]
fromFileR fname = readFile fname >>= return . toreal . lines

toFile :: (Num a, Show a) => String -> [a] -> IO ()
toFile fname = (writeFile fname) . unlines . (map show)

histogramM :: (Num a, Ord a) => [a] -> [a] -> IO [Int]
histogramM bin = return . (histogram bin)

count :: (Num a,Ord a) => (a, a) -> [a] -> Int
count (lo, hi) = length . filter (\x -> (lo<=x) && (x<hi))

bins :: (Num a) => [a] -> [(a, a)]
bins lst = zip lst (tail lst)

histogram :: (Num a, Ord a) => [a] -> [a] -> [Int]
histogram bin lst = map ((flip count) lst) $ bins bin

このプログラムは ghci から load して使う。

Prelude> :l Histogram.hs
[1 of 1] Compiling Histogram ( Histogram.hs, interpreted )
Ok, modules loaded: Histogram.
*Histogram>

ヒストグラムの作り方はたとえば、data.txt というファイルのデータを [Int] 型のリストとして読み込んで、[1..6] の範囲のヒストグラムを作るには次のようにする。

*Histogram> fromFile "data.txt"
[1,2,3,4,5]
*Histogram> fromFile "data.txt" >>= histogramM [1..6]
[1,1,1,1,1]

さらに、改訂版の Histogram モジュールではファイルのデータを実数型のリストとして読み込み、実数のリストでヒストグラムを作ることができる。

*Histogram> fromFileR "data.txt"
[1.0,2.0,3.0,4.0,5.0]
*Histogram> fromFileR "data.txt" >>= histogramM [0.5,1..5.5]
[0,1,0,1,0,1,0,1,0,1]

また、計算結果をファイルに書き出すには toFile 関数を使う。

*Histogram> fromFileR "data.txt" >>= histogramM [0.5,1..5.5] >>= toFile "out.txt"
*Histogram> fromFile "out.txt"
[0,1,0,1,0,1,0,1,0,1]

toFile 関数は [Int] 型のリストの書き出しだけでなく、[Double] 型の実数のリストも書き出せる。

*Histogram> fromFile "data.txt" >>= toFile "out.txt"
*Histogram> fromFileR "out.txt"
[1.0,2.0,3.0,4.0,5.0]

ところで、上のソースのどの部分が型クラス制約なのかというと、たとえば bins 関数の型シグネチャーの

bins :: (Num a) => [a] -> [(a, a)]

がそれだ、(Num a) => は型変数 a が Num クラスのインスタンスであることを指定している。しかし、このような型の指定の仕方をすると、Num クラスのインスタンスの Int 型や Double 型のデータのどちらにも bin 関数が使える。

*Histogram> bins [1..5]
[(1,2),(2,3),(3,4),(4,5)]
*Histogram> bins [1,1.5..3]
[(1.0,1.5),(1.5,2.0),(2.0,2.5),(2.5,3.0)]

(Num a) => というのは何か暗号的で理解できにくく見えるが、使ってみるとこんな便利なものはないことがわかった。ポイントは自分の定義したい関数にどんなデータ型を使いたいか、またそれらのクラスのどんな関数を使いたいかということだ。たとえば上のソースの count という関数の型シグネチャーは次のようになる。

count :: (Num a,Ord a) => (a, a) -> [a] -> Int
count (lo, hi) = length . filter (\x -> (lo<=x) && (x<hi))

count は数値型 (Num 型) のデータを扱いたいので Num a という型クラス制約を利用する。また <= や < という比較演算子を使うためには Ord a という型クラス制約が必要だ。使ってみると、簡単な理屈だ。しかし、このおかげで整数型でも実数型でも同じ count という関数で処理することができる。

型クラス制約 type class constraint はおすすめだ。

by tnomura9 | 2015-10-08 23:03 | Haskell | Comments(0)
<< 無限集合と真のクラス ヒストグラム その2 >>