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

Haskellのお勉強 その13

続、構造体スタイルとしての代数的データ型について

パターンマッチによるフィールドへのアクセス

データコンストラクタを使ったパターンマッチを利用することで、
data宣言した型のフィールドにアクセスすることができる。

data Anchor = A String String

compileAnchor (A url label) =

上記は、compileAnchor関数の引数として、Anchor型の値をとって、その1つめのフィールドに
変数urlを束縛し、2つめのフィールドに変数lableを束縛している。


フィールドラベル

フィールドの数が多くなると、パターンマッチでフィールドにアクセスするのが大変になる。
そこでフィールドラベルを用いることとなる。各フィールドに対してラベルをつけることで、
ピンポイントで代数的データ型のフィールドにアクセスすることが可能となる。

data Anchor = A { aUrl   :: String,
                  aLabel :: String }

compileAnchor (A { aUrl = u, aLabel = l }) = ‥‥

パターンマッチにフィールドラベルを使うと、フィールドの順番を気にしなくてすむというメリットと、
必要がなければすべてのフィールドを書かなくてもよいというメリットがあり、
結果的に可読性が増すというメリットがある。
ちなみに、フィールドラベルを使って宣言した場合でも、
通常のパターンマッチによるフィールドへのアクセスを併用することができる。


セレクタ

フィールドラベルを使って代数的データ型を宣言すると、
そのフィールドラベルと同名の関数が自動的に宣言されて、その関数を使ってフィールドの値を得ることができる。
このような関数のことを「セレクタ」と呼ぶ。そんだけ。

例えば、こんなの

data Anchor = A { aUrl   :: String,
                  aLabel :: String }

href :: Anchor
href = A "http://d.hatena.ne.jp/zecl/" "Bug Catharsis"

main = do print (aLabel href) 

"Bug Catharsis"と出力される。


フィールドの更新

Haskellには代入という概念がなく、変数は「値や式に便宜的につけた名前」ということだった。
したがって、一度作った「フィールドを持った型」の値について、
その一部のフィールドの値だけを変更するようなことはできない。
しかし、一部のフィールドの値だけを変更したいときは、
次のようにすれば特定のフィールドだけを別の値に変更した値を作り出すことができる。

data Anchor = A { aUrl   :: String,
                  aLabel :: String }

href :: Anchor
href = A "http://d.hatena.ne.jp/zecl/" "Bug Catharsis"

main = do
  print $ aUrl href
  print $ aLabel href
  print $ aUrl (href { aUrl = "http://www.yahoo.co.jp/" })
  print $ aLabel (href { aLabel = "Yahoo!JAPAN" })

で、実行結果

"http://d.hatena.ne.jp/zecl/"
"Bug Catharsis"
"http://www.yahoo.co.jp/"
"Yahoo!JAPAN"

もちろんこう書いたからといってhrefの値が変わるわけではなく、
hrefの値をもとにして一部のフィールドだけを別の値に変更した
新たな値を作り直しているにすぎないというところに注意。


多相的な型の宣言

data宣言を使うと、フィールドの型に型変数が含まれる代数的データ型も宣言することができる。
型変数を使って宣言したフィールドは、特定の型に縛られない多相的なフィールドとなる。


たとえば、

data Stack a = MkStack [a]

これは、型変数aを使って多相的な代数的データ型「Stack a」を宣言している。
データコンストラクタはMkStackで、最初のフィールドの型が[a]である、と定義している

注意が必要なのは、「Stack a」全体で型を表しているということで、
「Stack」は「型コンストラクタ」であるという点。
これまでの例で宣言してきたAnchor型は、型変数を使っていなかったので、
たまたま型コンストラクタと型名が一致していたというわけ。

この「Stack a」型の値は、次のようなものを生成することができるよ。

data Stack a = MkStack [a]

MkStack [True, False,True]         -- Stack Bool型の値を作成
MkStack ['a', 'b', 'c']            -- Stack Char型の値を作成
MkStack ["hogehoge", "piyopiyo"]   -- Stack String型の値を作成

コンストラクタとデータコンストラクタ

コンストラクタとデータコンストラクタ名前空間が分かれているので、
両方に同じ名前を使うこともできる。

たとえば、

data Anchor = Anchor String String

と定義すれば、最初に出てきたAnchorは型コンストラクタのAnchorで、
その次に出てくるAnchorはデータコンストラクタのAnchorとなる。
こうすると、「型の名前(型コンストラクタ)」と
「その型の値を作りたいときに使う名前」が同じになるので、慣れると便利かもしれない。