前回は,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 = ()

この先は会員の登録が必要です。有料会員(月額プラン)は初月無料!

日経 xTECHには有料記事(有料会員向けまたは定期購読者向け)、無料記事(登録会員向け)、フリー記事(誰でも閲覧可能)があります。有料記事でも、登録会員向け配信期間は登録会員への登録が必要な場合があります。有料会員と登録会員に関するFAQはこちら