主なデータ型

主なデータ型とリテラル

ML で利用できる基本データ型には以下のようなものがあります。

説明 リテラルの例
bool 論理型 atrue, false
int 整数型(符号付き整数型) 123, 0x1a, ~123 (負数)
word ワード型(符号なし整数型) 0w123, 0wx1a
real 実数型 3.14, ~3.14, 1.23e4, 1.23e~4
char 文字型 #"a", #"\n"
string 文字列型 "abc\n"
unit ユニット型(0-タプル) ()

複合型として,タプル型,レコード型,リスト型があります。

型(例) 説明 リテラルの例
int * int * string タプル型(値の組) (123, 456, "abc")
{ a : int, b : int, c : string } レコード型(ラベル付き値の集合) { a = 123, b = 456, c = "abc" }
int list リスト型(値の列) [3, 1, 4, 2, 5]

論理型

論理型の定数は true と false です。 演算には not (否定), andalso (論理積), orelse (論理和) が使えます。 and, or でなく andalso, orelse なのが奇妙ですが,これは and が別の用途に予約されているためです。

- not true;
val it = false : bool

- it orelse true;
val it = true : bool

数値型

数値型の演算には ~ (単項マイナス), + (加算), - (減算), * (乗算), abs (絶対値) などが使えます。 除算演算子には,/ (real 型用) と div (int型, word型用) の2種類があります。 剰余演算子は mod で,int型とword型で利用できます。

- 2 * 3;
val it = 6 : int
- ~ it;
val it = ~6 : int
- abs it;
val it = 6 : int

- 10.0 / 3.0;
val it = 3.33333333333 : real
- 10 div 3;
val it = 3 : int
- 10 mod 3;
val it = 1 : int

これらの算術演算子は,2引数が同一の型でなければ利用できません。 例えば 2 + 3.0 は型エラーになります。 real(2) + 3.0 のように,適切に型を揃える必要があります。

- 2 + 3.0;
stdIn:34.1-34.8 Error: operator and operand don't agree [literal]
  operator domain: int * int
  operand:         int * real
  in expression:
    2 + 3.0

- real(2) + 3.0;
val it = 5.0 : real
(補足) 整数型から実数型への変換には real 関数が利用できます。 実数型から整数型への変換には,ceil (∞方向丸め), floor (-∞方向丸め), trunc (0方向丸め), round (四捨五入) といった関数が利用できます。

関係演算子には,>, >=, <, <=, =, <> があります。 (real 型の値には ==<> は使えません。)

- 2 > 3;
val it = false : bool

- 2  3;
val it = true : bool

文字型/文字列型

詳細は割愛しますが,文字にも文字列にも,\n のようなエスケープシーケンスが利用できます。 また,文字列の連結には ^ 演算子を使います。

- "hello\n";
val it = "hello\n" : string

- "abc" ^ "def";
val it = "abcdef" : string

タプル型/ユニット型

タプル (tuple) は複数の値を組にしたものです。 例えば,整数 123 と文字列 "abc" のタプルは (123, "abc") のように書き,その型は int * string と書きます。

- (123, "abc");
val it = (123,"abc") : int * string

タプルの n (≥ 1) 番目の要素を取り出すには #n という関数を利用します。

- val t = (123, (456, "abc"));
val t = (123,(456,"abc")) : int * (int * string)

- #1 (#2 t);
val it = 456 : int

ユニット (unit) は要素数 0 のタプルです。 ユニット型の唯一の値は () であり,型名は unit です。

ユニット型は,例えば,意味のある値を返さない関数の返却型として使われたりします。 文字列を印字する print 関数の返却型はユニット型です。

レコード型

レコード (record) はラベル付き値の集合です。

- val taro = { name = "Taro", age = 25 };
val taro = {age=25,name="Taro"} : {age:int, name:string}

- #age taro;
val it = 25 : int

この例では,ラベルとして nameage を持つレコードを作成しています。 ここで作成したレコード taro の型は { age : int, name : string} です。 レコードの各要素を取り出すには,#name#age のような名前の関数(セレクタ関数)を使います。

リスト型

リスト (list) は要素を一列に連結して作られるデータ構造です。 整数のリストは [1, 2, 3] のように書き,型は int list と書きます。 要素数 0 のリスト(空リスト)は [] または nil と書きます。

- [1, 2, 3];
val it = [1,2,3] : int list

- nil;
val it = [] : 'a list

- [];
val it = [] : 'a list

ML のリストは等質的 (homogeneous) です。 すなわち,リストは同じ型の要素のみから構成されます([1, 1.0, #"a"] のようなリストは許されません)。

すべてのリストは nil:: という2つの構成子を用いて記述できます。 :: はしばしば "cons" と呼ばれ("construct" の意),第1引数に先頭要素,第2引数に後続のリストをとります。

角括弧を用いたリストの式 [1, 2, 3]1 :: 2 :: 3 :: nil の構文糖という位置づけです。 :: は右結合なので,1 :: 2 :: 3 :: nil の結合は 1 :: (2 :: (3 :: nil)) となります。

- 1 :: 2 :: 3 :: nil;
val it = [1,2,3] : int list

リストは nil:: によって再帰的に構成されるデータ構造であるため,再帰関数を多用する関数型言語において配列よりもよく使われます。

多相型

以下のプログラムは,恒等関数(引数の値をそのまま返す関数)を定義したものです。

- fun id x = x;
val id = fn : 'a -> 'a

- id 5;
val it = 5 : int

この id 関数の型が 'a -> 'a と推論されていることがわかります。 'a のようにアポストロフィで始まるパラメータは型変数 (type variable) と呼ばれ,任意の型を表します。 'a -> 'a は,id 関数が,例えば int -> int としても振る舞うし,char -> char 型としても振る舞うことを意味しています。

等値型

1 個のアポストロフィで始まる型変数は任意の型を表しますが,2 個以上のアポストロフィで始まる型変数 (''a など) は等値型のみを表します。 等値型 (equality type) とは,関数 = で等値比較可能な型のことです。bool, int, char, string などの離散的なデータ型は等値型であり,等値型から構成されるタプルやリストなどの複合型も等値型です。 ただし,real 型や関数の型などは等値型ではありません。

次のように,id 関数の引数の型を ''a と指定すると,非等値型には関数を適用できなくなることが分かります。

- fun id (x : ''a) = x
val id = fn : ''a -> ''a

- id 3.14;
stdIn:64.1-64.8 Error: operator and operand don't agree [equality type required]
  operator domain: ''Z
  operand:         real
  in expression:
    id 3.14

全ての等値型には,比較演算子 = および <> が使えます。