Haskell は用語の使い方が独自だが、考え方は C++やJavaとそんなに変わらない。
Haskell には、型によるエラーチェックを壊すような、ナル値や、ダウンキャスト (基底クラスから派生クラスへの型変換) がない。ダウンキャストがないので, void*
のような汎用ポインタも使えない。
Haskell のデータ型 (algebraic datatype; または単に型) が、C++, Javaの (具象) クラスに当たる。Haskell の型クラス (type class; または単にクラス) は、C++の抽象クラスやJavaのインタフェイス。
データ型は data
キーワードで宣言する。Haskell がややこしいのは、次の3つを同じdata
キーワードを使うこと。
また、構造体や共用体の派生で, レコード構文がある。
データ型の宣言の構文はこのようになっている;
topdecl | → | data => ]= constrs] [deriving]
| |
simpletype | → | 型名 型変数1 ... 型変数k | (k ≧ 0) |
constrs | → | constr1 | ... | constrn | (n ≧ 1) |
deriving | → | deriving ( dclass | (dclass1, ... , dclassn) )
| (n ≧ 0) |
simpletype では, k ≧ 0, つまり型変数が一つもなくても構わない.
constr の部分が宣言する内容。まとめて書くと複雑になるので、列挙型, 構造体, 共用体に分けて説明する。
なお, data宣言における context =>
の部分は, 規格上は残っているが、実装では廃止され、エラーになる。字面上, データ型の型変数に対する型クラスの制約を指定するように見えるが、そうではなく、コンストラクタの引数に対する制約になる。意味がない。
例。
data Color = Blue | Red | Green | White
型名は英大文字で始めること。
この例の Blue
などは, コンストラクタ (構築子). これらのコンストラクタが Color
型の値を生成する。
構文としては, コンストラクタの名前は、型名と同じでもいいし、違ってもいい。列挙型としての使い方だと, 型名と違う名前にすることが多い。
コンストラクタは英大文字で始まる必要がある。使うときには関数のように式の中で使用するが、普通の関数の名前とは違う。
構造体の宣言はこのように書く。まずは、フィールド名がない書き方。
data 型名 "=" 構築子 [ フィールドの型 ... ]
例。
data Point = Pt Int Int
この例の Pt
が構築子、二つのInt型のフィールドを取る。この用途では, 構築子は型名と同じにすることが多い。型が来る場所と関数が来る場所は構文上明らかなので、同名でも文脈から区別できる。
型の順序だけだと意味が自明ではないので、複雑になる場合は, 後述のレコード構文でフィールドに名前を付ける。
フィールド値を取り出すのは、こんな感じ。関数を作ってやる。
data Point = Pt Int Int pointX :: Point -> Int -- 型を書くところでは Point pointX (Pt x _) = x -- 式を書くところでは Pt pointY :: Point -> Int pointY (Pt _ y) = y main :: IO () main = do let pt = Pt 10 20 print $ pointX pt print $ pointY pt
上の列挙型と構造体を組み合わせたもの。
data データ型 "=" 構築子 [ フィールドの型 ... ] "|" 構築子 [ フィールドの型 ... ] | ...
例。
data Foo = Bar Int String | Baz Int Int Int
この例のBar
, Baz
はコンストラクタ。C++だとフラグのフィールドを用意して, 開発者がその値によってフィールドの意味を管理しなければならないが、Haskell ではコンストラクタで区別されるので、共用体のなかのフィールドの順序、型について、コンパイラが曖昧さなくエラーチェックできる。
各コンストラクタのフィールドの型、数は同じでなくても構わない。
構造体や共用体の各フィールドには、名前を付けることができます (レコード構文)。C++ / Javaのクラスに近いイメージです。
data データ型 "=" 構築子 "{" フィールド名 :: 型 , ... "}"
フィールド名は英小文字始まりです。
$ ghci > data Point = Pt { pointX :: Int, pointY :: Int } > let mypt = Pt { pointX = 10, pointY = 30 } > print (pointX mypt) 10
Point
型の値を作って, そのフィールド値を表示します。フィールド名の関数が自動的に作られるようなイメージです。あまり名前が被らないように, 型名をフィールド名の先頭に付けるといいでしょう。
data宣言で, 型名の後ろに型変数を付けることができる。
data 型名 型変数 ... = コンストラクタ [ "|
" コンストラクタ ... ]
例えば、任意の型を取れる MyPair
型を作ってみる。
data MyPair a b = MakePair a b pairFirst :: MyPair a b -> a pairFirst (MakePair x _) = x pairSecond :: MyPair a b -> b pairSecond (MakePair _ y) = y main :: IO () main = do let pair = MakePair 10.2 "fuga" :: MyPair Double String print $ pairFirst pair print $ pairSecond pair
data MyPair
に型変数を2つ付けている。
標準ライブラリの Maybe a
型は、次のようになっている。
data Maybe a = Nothing | Just a
Nothing
と Just
がコンストラクタ。
型クラスで関数のoverload を行う。Java などでのインタフェイスの位置づけ。class
キーワードで宣言する。
class Num a where (+) :: a -> a -> a negate :: a -> a
型クラスを実装する具象クラス (Haskell用語でデータ型) のことを、型クラスのインスタンスと呼ぶ。
Num a
の a
は型変数。型クラス Num
を実装するデータ型が a
になる。data宣言の型変数と使い方が異なる。
この型クラスはメソッド(+)
を持ち, その引数と戻り値は a -> a -> a
となる。
型 Int
は型クラスNum
を実装する。型がクラスを実装することを宣言するのは, instance
キーワードを使う。
instance Num Int where x + y = ... 定義の実装 negate x = ... 定義の実装
型クラスは、デフォルト実装を持つことができる。Java でもJava 8からインタフェイスにデフォルト実装を持てるようになった。C++でも抽象クラスにメソッド定義ができる。
class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool x /= y = not (x == y) x == y = not (x /= y)
デフォルト実装があることで、型クラスEq
のインスタンスでは, (==)
か (/=)
のどちらか片方だけ実装すればよい。
型に依存しないアルゴリズムなどは、型クラスのほうで定義することで、汎用的になる。ただ、使えるメソッドが限定されすぎる。すでに見たような、data宣言の多相型でもよい。
型クラスは, 別の型クラスから継承することができる。一方、データ型は継承できない。いわゆるオブジェクト指向言語が何でもかんでもクラス (Haskell用語だとデータ型) に機能が押し込まれているのを、分離したイメージ。
型クラス Ord
は, Eq
を継承している。
$ ghci > :i Ord class Eq a => Ord a where compare :: a -> a -> Ordering (<) :: a -> a -> Bool (<=) :: a -> a -> Bool (>) :: a -> a -> Bool (>=) :: a -> a -> Bool max :: a -> a -> a min :: a -> a -> a -- Defined in ‘GHC.Classes’ instance (Ord a, Ord b) => Ord (Either a b) -- Defined in ‘Data.Either’ instance Ord Integer -- Defined in ‘integer-gmp-1.0.0.0:GHC.Integer.Type’ instance Ord a => Ord [a] -- Defined in ‘GHC.Classes’ ...以下いろいろ続く
class宣言で, Eq a =>
のように書く。
データ型 (datatype) は data
キーワード。名前付けデータ型は newtype
キーワード, 別名は type
キーワードで宣言する。