プログラミングHaskellを読む (3)
第3章 型とクラス
基本
- Bool, Char, String*1, Int, Integer, Float
- リスト [a]
- 要素の型は同じ、型に「長さ」は含まない、長さに制限はない
- タプル (a, b)
- 関数 (a -> b)
- 全域関数*4である必要はない
- カリー化 (a -> b -> c -> ...)
- カリー化された関数は部分適用により、新たな関数を作成できる。
-- タプルを引数とする関数add add :: (Int, Int) -> Int add (a, b) = a + b -- カリー化された関数add' ("->"が右結合のため括弧は無くても良い) add' :: Int -> (Int -> Int) add' a b = a + b -- カリー化された関数を利用したadd100 add100 :: Int -> Int add100 = add' 100 {- 空白区切りの関数は左結合 add' 100 200 = (add' 100) 200 -}
多層型
型には変数を利用できる(型変数)。これにより任意の型を表現できる。
*Main> :t length length :: [a] -> Int *Main> length [1,2,3] 3 *Main> length "abc" 3 *Main> length [(+), (-), (*)] 3
型変数を一つでも含む型や、その型を持つ式を「多相的」という。
多重定義型
型には「クラス制約」を含めることが出来る。
*Main> :t (+) (+) :: Num a => a -> a -> a
"=>"以前のNum a
がクラス制約*8*9。
クラス制約を一つでも持つ型を多重定義型という。
数値も多重定義されている。
*Main> :t 3 3 :: Num a => a
クラス
- 型は関連する値の集合。
- 下の"Int"や"Int->Int->Int"。
let a = 10 :: Int add :: Int -> Int -> Int
- クラスは共通のメソッドを提供する方の集合。
- HaskellではJava的なクラスメソッドは無い。
- 関数をその型に適用出来れば、それがクラスメソッド。
- FractionalクラスはNumクラスのインスタンス
*Main> :t 1.0 1.0 :: Fractional a => a *Main> :t (+) (+) :: Num a => a -> a -> a *Main> (+) 1.0 1.0 2.0
おまけ
- Boolの語源はGeorge Boole
- カリー化とHaskellの語源はHaskell Curry
練習問題
1. 何型?
['a', 'b', 'c'] -- [Char] ('a', 'b', 'c') -- (Char, Char, Char) [(False, 'o'), (True, '1')] -- [(Bool, Char)] ([False, True], ['0', '1']) -- ([Bool], [Char]) [tail, init, reverse] -- [[a] -> [a]]
2. 何型?
second xs = head (tail xs) -- [a] -> a swap (x, y) = (y, x) -- (a, b) -> (b, a) pair x y = (x, y) -- a -> b -> (a, b) double x = x * 2 -- Num a => a -> a palindrome xs = reverse xs == xs -- [a] -> Bool twice f x = f (f x) -- (a -> a) -> a -> a
最後のむずいですね。
f x :: a -> b --"f x"は型"b"を返すので f (f x) :: b -> c {- f :: a -> b かつ f :: b -> c なので a = b = c つまり f :: a -> a よって twice f x :: (a -> a) -> a -> a -}
3. 答えの確認
*Main> :t ['a', 'b', 'c'] ['a', 'b', 'c'] :: [Char] *Main> :t ('a', 'b', 'c') ('a', 'b', 'c') :: (Char, Char, Char) *Main> :t [(False, 'o'), (True, '1')] [(False, 'o'), (True, '1')] :: [(Bool, Char)] *Main> :t ([False, True], ['0', '1']) ([False, True], ['0', '1']) :: ([Bool], [Char]) *Main> :t [tail, init, reverse] [tail, init, reverse] :: [[a] -> [a]] *Main> let second xs = head (tail xs) *Main> let swap (x, y) = (y, x) *Main> let pair x y = (x, y) *Main> let double x = x * 2 *Main> let palindrome xs = reverse xs == xs *Main> let twice f x = f (f x) *Main> :t second second :: [a] -> a *Main> :t swap swap :: (t1, t) -> (t, t1) *Main> :t pair pair :: t -> t1 -> (t, t1) *Main> :t double double :: Num a => a -> a *Main> :t palindrome palindrome :: Eq a => [a] -> Bool *Main> :t twice twice :: (t -> t) -> t -> t
palindromeを間違えた。
比較できないといけないからEq型じゃないとダメだった。
4. 一般的に関数の型をEqクラスのインスタンスにできない理由。逆にどういう場合に実現できるか。
まず、「関数が同等」というのがどういう意味かですが、「同等な引数に対して同等な結果を返す」ものが同等と言えます。
add a b = a + b add' a b = a + b - b + b
の場合のaddとadd'は、同じ引数を渡した時、結果も同じになるので、同等です。
Eqクラスのインスタンスであれば、当然のことながら比較はできますが、現在時刻を表示するなどの副作用のある関数は返す値が変わります。なので、全ての関数をEqクラスのインスタンスにすることは出来ません。
逆に言えば、副作用が無い関数ではEqクラスのインスタンスにすることは可能だと言えます。
(といった感じでしょうか。。。)