data Animal1 = Dog1 String
| Cat1 String Int
call1 :: Animal1 -> String
call1 (Dog1 name) = name
call1 (Cat1 name whiskers) = name ++ " with " ++ show whiskers ++ " whiskers"
test1 = mapM_ (putStrLn . call1) [Dog1 "pochi", Cat1 "tama" 12]
簡単だけれど、種類を増やす場合にはコンストラクタを増やした上で、パターンマッチの部分も増やさなくてはいけません。種類がおそらく増えないであろう場合、または変更が局所的であるほうがうれしい場合には良いかもしれません。
data Dog2 = Dog2 String
callDog :: Dog2 -> String
callDog (Dog2 name) = name
data Cat2 = Cat2 String Int
callCat :: Cat2 -> String
callCat (Cat2 name whiskers) = name ++ " with " ++ show whiskers ++ " whiskers"
data Animal2 = Animal2 { call2 :: String }
dog2 :: Dog2 -> Animal2
dog2 dog = Animal2 { call2 = callDog dog }
cat2 :: Cat2 -> Animal2
cat2 cat = Animal2 { call2 = callCat cat }
test2 = mapM_ (putStrLn . call2) [dog2 (Dog2 "pochi"), cat2 (Cat2 "tama" 12)]
種類を増やしても既存のコードを変更する必要がありませんが、変換用の関数を書くのが面倒かもしれません。
data Dog3 = Dog3 String
data Cat3 = Cat3 String Int
class Animal3Class a where
call3' :: a -> String
instance Animal3Class Dog3 where
call3' (Dog3 name) = name
instance Animal3Class Cat3 where
call3' (Cat3 name whiskers) = name ++ " with " ++ show whiskers ++ " whiskers"
data Animal3 where
Animal3 :: (Animal3Class a => a -> Animal3)
call3 :: Animal3 -> String
call3 (Animal3 a) = call3' a
test3 = mapM_ (putStrLn . call3) [Animal3 (Dog3 "pochi"), Animal3 (Cat3 "tama" 12)]
2番目の方法と基本的には同じ(型の変換を型システムが勝手にやってくれると考えれば良い?)だと思いますが、C++やJavaのようなオブジェクト指向な考え方をすると最も普通に考えられるかも知れません。
data Animal3 where
Animal3 :: (Animal3Class a => a -> Animal3)
のところは、Existentially quantified typesを使って以下のようにも書けます。
data Animal3 = forall a. Animal3Class a => Animal3 a