ようこそ。睡眠不足なプログラマのチラ裏です。

Haskellのお勉強 その15

共有体スタイルで代数的データ型を使う

共用体スタイルでは、たとえば次のような感じで代数的データ型を宣言する。

data PTItem = Param Int | Text String

上記のようにdata宣言を書くと、PTItem型が2つの代数的データ型「Param Int」と「Text String」の
いずれかであるという宣言となる。こうすると、「Param Int」と「Text String」はそれぞれ
独立のdata宣言で定義したのと同じように扱うことができる。

Text "asdfdsa"
Text "jkl;lkj"
Param 1114
Param 1231

共有体スタイルの代数的データ型のフィールドにアクセスするには、
構造体スタイルの場合と同様にパターンマッチを用いる。

data PTItem = Param Int | Text String

isText :: PTItem -> Bool
isText (Text _) = True
isText (Param _) = False

isNumeric :: PTItem -> Bool
isNumeric (Text _) = False
isNumeric (Param _) = True

text :: PTItem -> String
text (Text s) = s
text (Param i) = show i

main = do
  print $ isText (Text "asdfdsa")
  print $ isText (Text "jkl;lkj")
  print $ isText (Param 1114)
  print $ isText (Param 1231)

  print $ isNumeric (Text "asdfdsa")
  print $ isNumeric (Text "jkl;lkj")
  print $ isNumeric (Param 1114)
  print $ isNumeric (Param 1231)

  print $ text (Text "asdfdsa")
  print $ text (Text "jkl;lkj")
  print $ text (Param 1114)
  print $ text (Param 1231)

実行結果は

True
True
False
False
False
False
True
True
"asdfdsa"
"jkl;lkj"
"1114"
"1231"

ここではPTItem型の値が「Textというデータコンストラクタ」で生成されたのか、
あるいは「Paramというデータコンストラクタ」で生成されたのかをパターンマッチによって判別している。
このようにすると、共用体スタイルで宣言された代数的データ型の値について、
実際の値がどのデータコンストラクタを使っているかを判断することができる。

再帰的な型

data Stack a = Empty | Push a (Stack a)

上記のようにdata宣言を書くと、「Stack a」型は「Empty」か「Push a (Stack a)」の
いずれかだと定義したことになる。ここからは4つのことがわかる。
まず、共用体スタイルの宣言に、データコンストラクタのみの選択肢があってもかまわないこと。(ここではEmpty)
次に、共用体スタイルの宣言でも型変数を使って多相的な型を宣言できること。
そして、「(Stack a)」のように、型をまとめるためにカッコを使ってもいいこと。
最後に、代数的データ型の宣言中に、宣言中のその型自身を使ってもいいということがわかる。
このように定義した「Stack a」型は、次のようにして値を生成することができる。

Empty
Push 1 (Empty)
Push 2 (Push 1 (Empty))
Push 3 (Push 2 (Push 1 (Empty)))
Push 4 (Push 3 (Push 2 (Push 1 (Empty))))
Push 5 (Push 4 (Push 3 (Push 2 (Push 1 (Empty)))))

上記はいずれも「Stack a」型の値となる。
「Stack a」型の値に対してフィールドにアクセスしたいときは、次のように書く

data Stack a = Empty | Push a (Stack a)

isEmpty :: Stack a -> Bool
isEmpty Empty      = True
isEmpty (Push _ _) = False

top :: Stack a -> a
top (Push x _) = x

pop :: Stack a -> Stack a
pop (Push _ stk) = stk

x = Push 5 (Push 4 (Push 3 (Push 2 (Push 1 (Empty)))))
y = Push 5 $ Push 4 $ Push 3 $ Push 2 $ Push 1 $ Empty

main = do
  print $ top x
  print $ top $ pop x
  print $ isEmpty x
  print $ isEmpty $ pop $ pop $ pop $ pop $ pop $ x
  print $ isEmpty $ pop $ pop $ pop $ pop $ pop $ y


実行結果は

5
4
False
True