データ型とパターンマッチ
データ型の定義とパターンマッチの基本
これまでは主に既存のデータ型(整数やリスト)を扱ってきましたが、Haskellでは自分でデータ型を定義することもできます。
例えば、2個の整数の組を表すデータ型の定義は、次のようになります:
data IntPair = MkIntPair Integer Integer
data の直後の IntPair が型名です。Haskellにおいて関数名や変数名は小文字から始めますが、型名は大文字から始めます。
イコール =
の直後の MkIntPair はデータコンストラクター(データ構築子)と呼ばれ、IntPair 型の値を作るのに使えるほか、IntPair 型の値から構成要素であるIntegerを取り出す(パターンマッチ)のに使えます。データコンストラクターも大文字から始めます。
「IntPair型の値を作る」場合のデータコンストラクターは、関数として使えます。
MkIntPair :: Integer -> Integer -> IntPair
-- MkIntPair 1 2 :: IntPair
IntPair型の値から、要素である Integer の値を取り出すには、パターンマッチを使います。 例えば、組の2つの整数の和を計算する関数は、次のようになります:
f :: IntPair -> Integer
f (MkIntPair x y) = x + y
対話環境で、データ型の定義と関数の定義を試すには
Prelude> data IntPair = MkIntPair Integer Integer
Prelude> let f (MkIntPair x y) = x + y
Prelude> f (MkIntPair 3 5)
8
とすれば良いでしょう。
関数の引数に対してではなく、他の値(他の関数の結果など)についてパターンマッチを行う場合には、 case 式を使います。
さっきと同じ f を case 式を使って書くと、次のようになります:
f p = case p of { MkIntPair x y -> x + y }
of 以下の波かっこ { }
は、適切にインデントする場合は省略できます:
f p = case p of
MkIntPair x y -> x + y
なお、データコンストラクターと型名を同じにすることもできます:
data IntPair = IntPair Integer Integer
多相的なデータ型
さっきの IntPair の要素の型は Integer で固定でしたが、他の型についても似たような型を使いたいとしましょう。
data DoublePair = MkDoublePair Double Double
data StringPair = MkString String String
こんな時に、いちいち新しい型を定義しなくても、要素の型をパラメーターとして指定できるようにすると便利です。
Haskellでは、そのような多相的なデータ型を定義することができます。
data Pair a = MkPair a a
この Pair は型ではなく、「型を受け取って型を返す写像」と見なすことができます。このような写像を型コンストラクター(型構築子)と呼びます。さっきの IntPair 型に相当するのは Pair Integer 型になります。
成功と失敗を表す
計算や処理は失敗する場合があります。例えば、整数の割り算の場合は、分母が0だと失敗します。このような場合は例外を発生させることもあります:
Prelude> 1 `div` 0
*** Exception: divide by zero
…が、例外は扱いづらいので、通常の計算結果として失敗を表すことを考えましょう。
ここでは、「成功か失敗か」、そして「成功した場合の結果」を表せるデータ型を作り、計算結果としてそのデータ型の値を返すことにします。集合の直和みたいな感じです。
data DivisionResult = Successful Integer | DivisionByZero
safeDiv :: Integer -> Integer -> DivisionResult
safeDiv n m | m == 0 = DivisionByZero
| otherwise = Successful (n `div` m)
DivisionResult 型は、 Successful と DivisionByZero の2つのデータコンストラクターを持ちます。定義の際にはデータコンストラクターを縦棒 |
で区切ります。
この DivisionResult 型から場合分けをして値を取り出す場合も、パターンマッチが使えます。
isSuccessful :: DivisionResult -> Bool
isSuccessful (Successful _) = True
isSuccessful DivisionByZero = False
divisionResultOrZero :: DivisionResult -> Integer
divisionResultOrZero (Successful x) = x
divisionResultOrZero DivisionByZero = 0 -- ゼロ除算の場合は計算結果の代わりに 0 を返す
case 式を使うと、次のようにも書けます:
isSuccessful x = case x of { Successful _ -> True ; DivisionByZero -> False }
divisionResultOrZero x = case x of { Successful x -> x ; DivisionByZero -> 0 }
または、波かっことセミコロンの代わりにインデントを使って:
isSuccessful x = case x of
Successful _ -> True
DivisionByZero -> False
divisionResultOrZero x = case x of
Successful x -> x
DivisionByZero -> 0
実はこの DivisionResult 型をもっと一般化したものが Prelude で定義されています:
data Maybe a = Just a
| Nothing
Maybe は多相的で、2つのデータコンストラクターを持ちます。Just が「成功」を表し、 Nothing が「失敗」を表します。
課題:Maybe 型を使って、 safeDiv, isSuccessful, divisionResultOrZero のそれぞれの関数を定義せよ。
もっと複雑な型
もっと複雑な型を定義してみましょう。
「定数と足し算、引き算、掛け算からなる式」を表す型を作ってみましょう。
data Expr = Const Integer
| Add Expr Expr
| Sub Expr Expr
| Mul Expr Expr
deriving (Eq,Show)
このように、データ型を定義する際に、中身の型としてそのデータ型自身(この場合は Expr)を使うことができます。
(deriving (Eq,Show)
は、コンパイラーに Eq クラスと Show クラスのインスタンスを適当に作ってもらうための指示です。)
この Expr 型で表される式は、例えば Mul (Add (Const 1) (Const 2)) (Const 3)
となります。
このような式を計算する関数 eval は、次のように定義できます:
eval :: Expr -> Integer
eval (Const n) = n
eval (Add x y) = eval x + eval y
eval (Sub x y) = eval x - eval y
eval (Mul x y) = eval x * eval y
標準ライブラリーのデータ型
Haskellには言語組み込み、あるいは標準ライブラリー(Prelude)で、いくつかのデータ型が定義されています。
単位型 ()
単位型は、値を1つだけ持つ型です。唯一の値(データコンストラクター)も同じ空かっこ ()
で表されます。
ちなみに、空かっこ ()
を型名やデータコンストラクターとする型は自分で定義できません。同等な型を自分で定義するとすれば、次のようになるでしょう:
data Unit = Unit
タプル (,)
2種類の型の組です。集合の直積みたいなものです。
Int と String の組であれば、(Int,String)
型となります。値(データコンストラクター)の記法も (x,y)
です。
型コンストラクターとデータコンストラクターを単独で書く状況では、カッコの中にカンマだけ (,)
を書いて表します。
Prelude> :t (,)
(,) :: a -> b -> (a, b)
これも名前が特殊なので、このような型名およびデータコンストラクターを持つ型は自分で定義できません。同等な型を自分で定義するとすれば、次のようになるでしょう:
data Pair a b = Pair a b
Maybe
さっきも説明したように、「成功か失敗か」および「成功した場合の値」を持ちます。定義は次の通りです:
data Maybe a = Just a
| Nothing
Either
2種類の型のいずれか一方を表します。集合の直和みたいなものです。
これも Maybe と同じように、「成功か失敗か」を表すのに使われますが、「成功時の値」だけではなく「失敗時の値」(エラーメッセージ等)も持てるのが違いです。Right が成功時の値に使われることが多いようです。
データコンストラクターは Left と Right です。
data Either a b = Left a | Right b
リスト []
すでに以前のページで扱ったと思います。データコンストラクターは空リスト []
とコロン (:)
です。
これも型名とデータコンストラクターの名前が特殊です。あえてHaskell風の定義を書くとすれば、次のようになるでしょうか:
data [a] = [] -- 空リスト
| (:) a [a] -- 先頭の要素と、残り
同等な型を自分で定義するとすれば、次のようになるでしょう:
data List a = Nil
| Cons a (List a)