deriving修飾子

新しく宣言した型を何らかの型クラスのインスタンスとして宣言するには,instance 宣言を使う。が,いくつかの型クラスに関してはもっと簡単な方法がある。data 宣言と一緒に deriving 修飾子を使うのがそれ。
次のようにすると,Cream 型を宣言すると同時に,Eq,Show,Read クラスのインスタンスとして宣言できる。

data Cream = Eric | Jack | Ginger deriving (Eq, Show, Read)
*Main> Eric == Eric
True
*Main> Eric /= Jack
True
*Main> show Ginger
"Ginger"

deriving で指定できるのは,Eq,Ord,Enum,Bounded,Show,Read の6つだけ。この6つは頻繁に使われるからこういう簡便な方法が用意されているってことのようだ。とくに ghci で表示させるには Show クラスのインスタンスである必要があるので,黙って指定しておくべきだろう。

で,これらのクラスにどんなメソッドがあるのか,調べてみた。

  • Eq クラス
    • (==) :: a -> a -> Bool
    • (/=) :: a -> a -> Bool
  • Ord クラス
    • compare :: a -> a -> Ordering
    • (<) :: a -> a -> Bool
    • (<=) :: a -> a -> Bool
    • (>) :: a -> a -> Bool
    • (>=) :: a -> a -> Bool
    • max :: a -> a -> a
    • min :: a -> a -> a
  • Enum クラス
    • succ :: a -> a
    • pred :: a -> a
    • toEnum :: Int -> a
    • fromEnum :: a -> Int
    • enumFrom :: a -> [a]
    • enumFromThen :: a -> a -> [a]
    • enumFromTo :: a -> a -> [a]
    • enumFromThenTo :: a -> a -> a -> [a]
  • Bounded クラス
    • minBound :: a
    • maxBound :: a
  • Show クラス
    • showsPrec :: Int -> a -> Shows
    • show :: a -> String
    • showList :: [a] -> Shows
  • Read クラス
    • readsPrec :: Int -> ReadS a
    • readList :: ReadS [a]

いっぱいあるな,とくに Ord クラスと Enum クラスが。
ところで上のリストは Hoogle で調べたんだけど,Read クラスのメソッドに read がない。と思ったら別のところに書いてあった。これってつまり,read は Read クラスのメソッドじゃないってことか?

練習問題

入門Haskell―はじめて学ぶ関数型言語」 p.101 より。

ところで,あるリストに複数の型を含めたい場合にはどうしたらいいか,という話ですが,そういう場合にも data は便利です。たとえば次のように
data MyType = I Int | F Float | C Char
ここでたとえば [I 10, F 10, C ‘a’] のようなリストがあるとします。

(1)[MyType] から C 要素だけ取り出す関数 filterC を書きなさい。

これは C 要素であるか否かを判定する関数を作って filter すればいいんだろう。判定には case を使って場合分けすればいい。

filterC :: [MyType] -> [MyType]
filterC = filter isC
  where isC x = case x of
                I a -> False
                F a -> False
                C a -> True

結果。

*Main> filterC [I 10, F 10, C 'a']
[C 'a']

OK。

(2) [MyType] から I 要素だけを取り出してその合計を計算する sumI を書きなさい。

sumI :: [MyType] -> Int
sumI = sum . map pickI
  where pickI x = case x of
                  I a -> a
                  otherwise -> 0
*Main> sumI [I 10, F 10, C 'a']
10

なるほど,otherwise も使えるんだな。

(3) [MyType] から,I 要素はそのまま,F 要素は整数にし(truncate 関数が使えます),C 要素はその文字の文字コードを計算し( Data.Char をインポートすれば ord 関数が計算してくれます),その合計を計算する mySum 関数を書きなさい。

import Data.Char
mySum :: [MyType] -> Int
mySum = sum . map toInt
  where toInt x = case x of
                  I a -> a
                  F a -> truncate a
                  C a -> ord a
*Main> mySum [I 10, F 10, C 'a']
117

‘a’ のコードは 97 だから全部足すと 117。OK。

ところで,GHCi で表示できるように,MyType の宣言は次のようにしている。

data MyType = I Int | F Float | C Char deriving (Show)