前回のH本読書会*1にて、Integralという型クラスの説明があったのですが(P33)、上手く解説できなかったので調べてみた。
とりあえず、型の定義を。
Prelude> :i Integral
class (Real a, Enum a) => Integral a where
quot :: a -> a -> a
rem :: a -> a -> a
div :: a -> a -> a
mod :: a -> a -> a
quotRem :: a -> a -> (a, a)
divMod :: a -> a -> (a, a)
toInteger :: a -> Integer
-- Defined in `GHC.Real'
instance Integral Integer -- Defined in `GHC.Real'
instance Integral Int -- Defined in `GHC.Real'
どうやら、RealとEnumの型制約が付いているようです。
Realは、実数ですね。Enumは順序を示す型クラス。つまり順序立てる事ができる実数を特化したものがIntegralという型になるということです。
instanceを見ると、IntegerとIntが挙げられています。どうやら、IntegerとIntという似た者同士をまとめて扱えるようです。
関数を見てみると、quotやdivやmodなど掛けたり割ったりするようです。そして、toIntegerがあるので、IntegerとIntは、Integralを通してIntegerに変換することが出来るみたいですね。
そして本に解説のあった、「fromIntegral」関数ですが、IntとIntegerを引数にとって、Num型(Numは、IntやIntegerも属していますが、その他あらゆる(?)数値型を汎化したもっと守備範囲の広い型)に変換する関数なようです。
fromIntegral :: (Integral a, Num b) => a -> b
Haskellには暗黙的な自動変換がないので、演算に必要な型は自分で揃えてあげる必要があります。
例えば(+)(プラス)演算の定義はこんな感じになっているようです。
class Num a where
(+) :: a -> a -> a
一見、引数が型パラメータ(a)となっていますので、Numのインスタンスならどんな型でも合いそうですが、(a -> a)となっている以上、第一引数と第二引数は同じ型でないとエラーとなります。
(1::Double) + (1::Int)
つまり、この様な例では、引数が(a -> b)となるため、型が合わずエラーです。
(1::Double) + fromIntegral (1::Int)
そこで、fromIntegralの出番となり、明示的に型変換してあげることにより、コンパイルできるようになるわけです。
型って素晴らしいですね。