Skip to content

Instantly share code, notes, and snippets.

@propella
Created December 18, 2010 08:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save propella/955a85be2ea40b8fd2fc to your computer and use it in GitHub Desktop.
Save propella/955a85be2ea40b8fd2fc to your computer and use it in GitHub Desktop.
how to make a heterogeneous list in Haskell.
> {-# OPTIONS -XExistentialQuantification #-}
> {-#LANGUAGE GADTs #-}
* Haskell で色々な型を混ぜて計算する方法。
動的型言語の特徴の一つに、配列に色々な型のデータを混ぜられる事がありま
す。例えば Javascript では ["1", 2, 3.4] のような配列を作る事が出来ます。
Haskell のような強い型付けのある言語でこれを実現する方法を勉強したので書きます。
目標として、配列の中にあるデータを文字列として連結する事にします。Javascript で言うと、
append = function (xs) {
var result = xs[0];
for (var i = 1; i < xs.length; i++) result += xs[i];
return result;
}
のような関数 append を作ります。append(["1", 2, 3.4]) の答えは "123.4"
になります。
** 代数データ型を使う
Haskell では直接リストに別の型のデータを入れる事が出来ないので、ある型
で包んで入れる事にします。多分一番素直な方法は代数データ型を使う事だと思います。
> data Val0 = S String
> | I Int
> | F Float deriving (Show)
これで色んな型を配列に入れる事自体は出来ます。
*Main> [S "1", I 2, F 3.4]
[S "1",I 2,F 3.4]
文字列の連結をするには、一旦内容を文字列に変換すれば良いです。任意のデー
タを文字列に変換するには show という関数が使えます。
> toString0 (S x) = show x
> toString0 (I x) = show x
> toString0 (F x) = show x
> append0 :: [Val0] -> String
> append0 [] = ""
> append0 (x:xs) = toString0 x ++ append0 xs
*Main> append0 [S "1",I 2,F 3.4]
"\"1\"23.4"
ちょっと Javascript と実行結果が違うけど、説明の都合なので気にしないで下さい。
** Existential type
さて、上で定義した Val0 をよく見てみます。Haskell では String, Int,
Float 以外にも沢山型があります。もしも他の型に対応しようと思ったら単に
Val0 に適当に型定義と toString を追加すれば良いだけですが、なんか馬鹿馬
鹿しい感じもします。show が定義されている型なら同じ方法で文字列に変換出
来るので、わざわざ新しく作らなくても良い方法がありそうな物です。
Existential type を使うとそれが出来ます。 Existential type を使うには、{-# OPTIONS -XExistentialQuantification #-} というオプションを付けます。
上の例では、S や I 等のコンストラクタを使い型ごとに toString で文字列の
変換方法を指定しました(結局全部 show しただけですが)。次の例では、
forall a. (Show a) => という書き方によって単一のコンストラクタ Val の後
に、Show 型クラスのオブジェクトなら何でも使えるという指示をしています。
この forall a. という表記は冗長な気がするんだけど何で要るんだろう。。。
> data Val = forall a. (Show a) => V a
> toString :: Val -> String
> toString (V x) = show x
> append :: [Val] -> String
> append [] = ""
> append (x:xs) = toString x ++ append xs
*Main> append [V "1",V 2,V 3.4]
"\"1\"23.4"
というわけで、案外簡単に色んな型を含む配列を作る事が出来ると分かりました。
** GADT
もう一つのやり方として、GADT というのもあります。GADT を使うには
{-#LANGUAGE GADTs #-} を付けます。GADT では、コンストラクタが受け付ける
型を関数の型のような形で定義します。文法的にはこっちの方が分かりやすい
気がします。どうやって使い分けるんだろう。
> data Val1 where
> V1 :: Show a => a -> Val1
> toString1 :: Val1 -> String
> toString1 (V1 x) = show x
> append1 :: [Val1] -> String
> append1 [] = ""
> append1 (x:xs) = toString1 x ++ append1 xs
*Main> append1 [V1 "1",V1 2, V1 3.4]
"\"1\"23.4"
** まとめ
Haskell で配列に色々な型のデータを入れるには、代数型、Existential type、GADT のどれかを使えばいいです。
> main = do
> putStrLn (append0 [S "1",I 2,F 3.4])
> putStrLn (append [V "1",V 2,V 3.4])
> putStrLn (append1 [V1 "1",V1 2,V1 3.4])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment