前回は,OCamlでライブラリやプログラムを構築するための仕組みである「モジュール・システム」について述べた。では,それらのプログラムが処理するデータ(値)とその型は,どのように定義できるのだろうか?

値の組(タップル)

 まず,整数や浮動小数といったプリミティブ(原始的値)は,すでに何度も登場したのでよいと思う。また,前回も使用したが,OCamlを始めとする多くの関数型言語には,複数の値を組み合わせて一つの値とするタップル(組)というデータ構造がある。特に,二つの値の組のことをペア(二つ組)という。

 OCamlでは,値をコンマで区切って並べることにより組を表す。

# (1.2, 3.45) ;;
- : float * float = (1.2, 3.45)
# (123, 4.5, "abc") ;;
- : int * float * string = (123, 4.5, "abc")

 ちなみに,組の前後には通常は括弧を付けるが,OCamlでは実は省略することもできる。

# 123, 4.5, "abc" ;;
- : int * float * string = (123, 4.5, "abc")

 しかし,他の構文との優先順位などで混乱することも多いので,むやみに省略しないほうが無難だろう。

 上の出力結果を見てもわかるように,組の型は,かけ算の記号でもあるアスタリスクを流用して「型1 * 型2 * 型3 * …」のように表される。組の一つ目の値が型1を持ち,二つ目の値が型2を持ち,三つ目の値が型3を持ち…,という意味だ(かけ算の記号を使う理由については後で説明する)。

 組の中から値を取り出すには,パターン・マッチングという仕組みを使う。次の例では,組xを(a, b, c)というパターンにマッチさせることにより,xの中の値123, 4.5, "abc"を取り出して,それらをa, b, cと置いている。

# let x = (123, 4.5, "abc") ;; (* 組xを定義 *)
val x : int * float * string = (123, 4.5, "abc")
# match x with (a, b, c) -> (* 組xから値a, b, cを取り出す *)
    Printf.printf "a = %d, b = %f, c = %s\n" a b c ;;
a = 123, b = 4.500000, c = abc
- : unit = ()

 組やパターンはネストできる。つまり,下のように「組の組」を作ったり,その中身をパターン・マッチングで一気に取り出したりできる。

# let y = (x, x) ;; (* 組の組を作る *)
val y : (int * float * string) * (int * float * string) =
  ((123, 4.5, "abc"), (123, 4.5, "abc"))
# match y with ((a1, b1, c1), (a2, b2, c2)) -> (* その中身を一気に取り出す *)
    Printf.printf "a1 = %d, b1 = %f, c1 = %s\n" a1 b1 c1;
    Printf.printf "a2 = %d, b2 = %f, c2 = %s\n" a2 b2 c2 ;;
a1 = 123, b1 = 4.500000, c1 = abc
a2 = 123, b2 = 4.500000, c2 = abc
- : unit = ()

 パターン・マッチングにおいて,値を無視したいときは,アンダースコア記号を使う。これは"don't care"パターンと呼ばれ,一部の値だけ取り出したいときなどによく利用する。

# match x with (a, _, _) -> (* 三つ組xの第1要素だけ取り出す *)
    Printf.printf "a = %d\n" a ;;
a = 123
- : unit = ()

要素に名前のついた組(レコード)

 上で述べたように,組を使えば複数の値を一つにまとめることが可能だ。しかし,例えば文字列の三つ組で「氏名と住所と電話番号」を表しても,単に「string * string * string」という型になってしまい,どのstringが何を表しているのかわかりにくい。

 そうしたときには,組の要素に名前(ラベル)をつけた値であるレコードを使えばよい。レコードの構文は次の通りだ。

{ ラベル名1 = 値1; ラベル名2 = 値2; ラベル名3 = 値3; ... }

 ただし,OCamlでは,レコードを使うためには,あらかじめ以下のようにレコードの型を宣言しておく必要がある。

type 型名 = { ラベル名1 : 型1; ラベル名2 : 型2; ラベル名3 = 型3; ... }

 ちなみに,このようなレコードについての制限をきちんと解消したML系言語の処理系としては,東北大学の大堀淳教授らが開発しているSML#がある(なお,SML#はC#よりずっと以前から存在しており,.NETと直接の関係はない)。

 組と同じく,レコードの中の値も,パターン・マッチングを使って取り出すことができる。以下にレコードの定義と使い方の例を示す。

# (* レコード型の定義 *)
  type person = { name : string; addr : string; phone : string } ;;
type person = { name : string; addr : string; phone : string; }
# (* レコード値の定義 *)
  let r =
    { name = "Nikkei Taro";
      addr = "Minatoku Shirokane 1-17-3";
      phone = "03-xxx-yyyy" } ;;
val r : person =
  {name = "Nikkei Taro"; addr = "Minatoku Shirokane 1-17-3";
   phone = "03-xxx-yyyy"}
# (* レコードのパターン・マッチング *)
  match r with { name = n; addr = _; phone = p } ->
    Printf.printf "%s: %s\n" n p ;;
Nikkei Taro: 03-xxx-yyyy
- : unit = ()

値の場合分け(バリアント)

 上で述べたように,組やレコードは「intとfloatとstring」ないし「nameとaddrとphone」のように,複数の値の組み合わせを表す。これに(ある意味で)対応するデータ構造として,「XまたはYまたはZ」のように,複数の型の場合分けを表すバリアントがある(ちなみに,第5回で紹介した多相バリアントは,通常のバリアントの拡張になっている)。

 例えば,何らかの処理が成功したら整数値を返し,そうでなければエラー文字列を返す,という関数を定義したかったら,以下のようなバリアント型iresultを利用できる。

# type iresult = ISuccess of int | IFailure of string ;;
type iresult = ISuccess of int | IFailure of string
# let div x y =
    if y = 0 then
      IFailure "division by zero"
    else
      ISuccess (x / y) ;;
val div : int -> int -> iresult = <fun>

 実際に関数を呼び出してみると,次のようになる。

# div 123 4 ;;
- : iresult = ISuccess 30
# div 56 0 ;;
- : iresult = IFailure "division by zero"

 ここで,「ISuccess」や「IFailure」は,バリアントがどの値を取っているかを表すタグで,コンストラクタとも呼ばれる。ISuccessやIFailureなどのタグに,30や"division by zero"などの値を与えると,バリアント型の値を作ることができるからだ。なお,OCamlでは,コンストラクタの頭文字は大文字でなければならない。

 一般に,バリアント型は以下の構文で定義できる。

type 型名 = タグ1 of 型1 | タグ2 of 型2 | タグ3 of 型3 | ...

 ただし,タグだけ必要で,値が不要の場合は,of以降を省略することもできる。例えば,曜日を表すバリアント型は次のように定義できる。

# type day = Sun | Mon | Tue | Wed | Thu | Fri | Sat ;;
type day = Sun | Mon | Tue | Wed | Thu | Fri | Sat

 論理値を表すbool型の値も,実は下のように定義されるバリアントとみなすことができる。

type bool = true | false

 ただし,OCamlではbool型はプリミティブになっており,上のような定義は不要である。

 バリアント型の値を処理したいときには,組の処理と同様に,パターン・マッチングを用いる。例えば,与えられた曜日が平日かどうかを返す関数は,次のように書ける。

# let weekday d =
    match d with
      Sun -> false | Mon -> true | Tue -> true | Wed -> true
    | Thu -> true | Fri -> true | Sat -> false ;;
val weekday : day -> bool = <fun>

 あるいは,以下のように,複数のパターンを縦棒で区切り,一つにまとめて書くこともできる。

# let weekday d =
    match d with
      Sun | Sat -> false
    | Mon | Tue | Wed | Thu | Fri -> true ;;
val weekday : day -> bool = <fun>

 さらに,OCamlのパターン・マッチングは前から順に行われるので,先に出てきたdon't careパターンを使って,下のように書いてもよい。

# let weekday d =
    match d with
      Sun | Sat -> false
    | _ -> true ;;
val weekday : day -> bool = <fun>

 実際に,weekday関数をday型の値に適用してみると次のようになる。

# weekday Wed ;;
- : bool = true
# weekday Sat ;;
- : bool = false

 先のiresult型のように,パラメータがある場合も同様だ。例えば,iresult型の値をprintする関数は,以下のように定義できる(もっとも,対話環境では元から結果の値が表示されるので,printすること自体にはあまり意味がないが)。

# let print_iresult r =
    match r with
      ISuccess i -> Printf.printf "%d\n" i
    | IFailure s -> Printf.printf "error: %s\n" s ;;
val print_iresult : iresult -> unit = <fun>
# print_iresult (div 123 4) ;;
30
- : unit = ()
# print_iresult (div 56 0) ;;
error: division by zero
- : unit = ()