2006-09-08 [長年日記]

[Haskell] 多相型っぽいリスト

もっとも簡単そうな方法

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)]

種類を増やしても既存のコードを変更する必要がありませんが、変換用の関数を書くのが面倒かもしれません。

GADT (Generalised Algebraic DataType)を使う方法

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
[]