データ型の定義

1. datatype 宣言

次のプログラムは,長方形の大きさを表すデータ型 rectangle と,長方形の面積を求める関数 area を宣言したものです.

- datatype rectangle = RECT of real * real;
datatype rectangle = RECT of real * real

- fun area (RECT (w, h)) = w * h;
val area = fn : rectangle -> real

- val r = RECT (3.0, 4.0);
val r = RECT (3.0,4.0) : rectangle

- area r;
val it = 12.0 : real

キーワード datatype で始まる宣言により,新しいユーザ定義型を定義できます. datatype 宣言で定義されるデータ型は,代数的データ型(algebraic data type)と呼ばれます.

この例で定義されている rectangle型構成子(type constructor),RECT値構成子(value constructor)と呼ばれます. 型構成子と値構成子には,同じ名前を用いることもできます.

一つの型に複数の値構成子を定義することもできます. 次のプログラムは,円または長方形を表すデータ型 shape を定義したものです.

- datatype shape = CIRC of real
                 | RECT of real * real;
datatype shape = CIRC of real | RECT of real * real

- fun area (CIRC r) = 3.14 * r * r
    | area (RECT (w, h)) = w * h;
val area = fn : shape -> real

- val c = CIRC 2.0;
val c = CIRC 2.0 : shape

- area c;
val it = 12.56 : real

次のプログラムは,曜日を表すデータ型 dayofweek を宣言したものです. このように,引数のない値構成子のみからなるデータ型は,特に列挙型(enumerated type)と呼ばれます.

- datatype dayofweek = SUM | MON | TUE | WED | THU | FRI | SAT;
datatype dayofweek = FRI | MON | SAT | SUN | THU | TUE | WED

- fun is_holiday SAT = true
    | is_holiday SUN = true
    | is_holiday _   = false;
val is_holiday = fn : dayofweek -> bool

- is_holiday MON;
val it = false : bool

次のプログラムは,リスト型を模した型を宣言したものです. このように,型引数を導入して多相型を構成することもできます.

- datatype 'a list' = NIL | CONS of 'a * 'a list';
datatype 'a list' = CONS of 'a * 'a list' | NIL

- fun length' NIL = 0
    | length' (CONS (x, xs)) = 1 + length' xs;
val length' = fn : 'a list' -> int

- length' (CONS(1, CONS(2, CONS(3, NIL))));  (* length [1,2,3] 相当 *)
val it = 3 : int

2. 型シノニム

存在するデータ型に別名を与えるには,以下のように type 宣言を使います

- type 'a collection = 'a list;
type 'a collection = 'a list

3. 抽象データ型

ここでの抽象データ型(abstract data type)とは,値構成子が隠蔽された型(実装が隠蔽された型)のことをいいます. 抽象データ型を使うことにより,データの実装を隠蔽しつつ,データへのアクセス手段のみをユーザに提供することができます.

次のプログラムは,スタックを表すデータ型 'a stack と,スタックを操作する関数群を宣言したものです. このプログラムでは,'a stack を抽象データ型として宣言しています.

(* file: stack1.sml *)

abstype 'a stack = NIL | INS of 'a * 'a stack
with
  (* 新規スタック *)
  val new = NIL

  (* 要素を先頭に追加する *)
  fun push (x, st) = INS (x, st)

  (* 先頭要素を取り出す *)
  fun pop (INS (x, st)) = (x, st)
end

抽象データ型はキーワード abstype で始まる宣言により記述します. with から end までの間には,抽象データ型のデータへのアクセス手段となる関数の宣言を記述します.

このプログラムを対話環境で読み込んで,少し遊んでみましょう.

- use "stack1.sml";
[opening stack1.sml]
type 'a stack
val new = - : 'a stack
val push = fn : 'a * 'a stack -> 'a stack
val pop = fn : 'a stack -> 'a * 'a stack
val it = () : unit

- val st = new;
val st = - : 'a stack

- val st = push ("a", st);
val st = - : string stack

- val st = push ("b", st);
val st = - : string stack

- val (x, st) = pop st;
val x = "b" : string
val st = - : string stack

- val (x, st) = pop st;
val x = "a" : string
val st = - : string stack

実行結果が val st = - : string stack と表示されていることからわかるように,スタックのデータの実装がユーザ側から見えないようになっていることがわかります.

4. 標準のデータ型

標準で定義されている boolint などのデータ型も,datatype 宣言で宣言された代数的データ型とみなすことができます. 例えば,bool 型は次のように宣言されているものと見ることができます.

datatype bool = true | false

int 型も次のように宣言されているものとみなすことができます(実際にすべての整数を列挙することはできませんが…​).

datatype int = ... | -2 | -1 | 0 | 1 | 2 | ...

標準で定義されているデータ型のうち未紹介のものに,順序型とオプション型があります. 順序型 order は次のように宣言されたデータ型で,順序を表すのに使います.

datatype order = LESS | EQUAL | GREATER

オプション型 'a option は次のように宣言されたデータ型で,値が存在するかしないか不明な文脈で使われます. (Haskell の Maybe 型,C# のヌル許容型などがこれに相当します.)

datatype 'a option = NONE | SOME of 'a

次のプログラムは,リストの n (≥ 0) 番目の値をオプション型で返す関数 nth を定義したものです. オプション型は,このように計算結果が存在するか分からない関数の返却型としてよく使われます.

- fun nth (nil,   _) = NONE
    | nth (x::xs, 0) = SOME x
    | nth (x::xs, n) = nth (xs, n - 1);
val nth = fn : 'a list * int -> 'a option

- nth (["a", "b", "c"], 1);
val it = SOME "b" : string option

- nth (["a", "b", "c"], 5);
val it = NONE : string option