OCaml入門(3)

このページは最後に更新されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

last mod. 2008-04-07 (月) 23:51:54
last mod. 2008-04-07 (月) 23:51:54

レコードとバリアント

以前リストとタプルについては述ましたが、それ以外にも、C の struct に相当する レコード(record)や、enum に対応する バリアント(variant)が存在します。

レコードは、{ } で括って表します。使う為には、まず レコードの型を定義する必要があります。ここでは、名前と郵便番号をセットで持つ レコードを定義しましょう。このとき、各要素には型をつけなければいけません。型は、: の後に型名を書く事によって表現することができます。

# type t = {
    name : string;
    zip  : string;
  };;

type t = { name : string; zip : string; }

これで、型 t は、name 及び zip という要素をもつレコード型として定義されました。しかし、これではただ型を定義しただけです。このレコード型のインスタンスを作成するには、次の様にして下さい。

# { name = "Sakura";
    zip  = "100-1234";
  };;

- : t = {name = "Sakura"; zip = "100-1234"}

レコード型の中で多相型を使う事もできます。この場合、型の宣言は次の様にします。'a を明示的に書かなければならないことに注意して下さい。

# type 'a t = {
    name : 'a;
  };;

type 'a t = { name : 'a; }
# { name = "mocchi" };;

- : string t = {name = "mocchi"}
# { name = 23 };;

- : int t = {name = 23}

特定のレコードの要素の値を取り出したい場合は、. を使うことで可能となります。

# let s = { name = "Takeo"; address = "Japan"; };;

val s : t = {name = "Takeo"; address = "Japan"}
# s.name;;

- : string = "Takeo"

既にあるレコードから、値を少々変えることで新しいレコードを作りたい場合は、次の様に with を付けることで実現することが出来ます。この操作は、レコードの値を変えているのではなく、新しいレコードを、既存のレコードの値を使って作成しているということに注意して下さい。

# type t = {
    name : string;
    address : string;
  };;

type t = { name : string; address : string; }
# let s = { name = "Takeru"; address = "Japan" };;

val s : t = {name = "Takeru"; address = "Japan"}
# { s with
    name = "Takeshi" };;

- : t = {name = "Takeshi"; address = "Japan"}

値を変えたい場合は、レコードの型宣言時に mutable を付けます。値の変更は、"<-" で行うことが出来ます。

# type t = {
    mutable name : string;
    mutable address : string;
  };;

type t = { mutable name : string; mutable address : string; }
# let s = { name = "Takeo"; address = "Japan"; };;

val s : t = {name = "Takeo"; address = "Japan"}
# s.name;;

- : string = "Takeo"
# s.name <- "Takeru";;

- : unit = ()
# s;;

- : t = {name = "Takeru"; address = "Japan"}

このとき、束縛している s の内容が実質変わっていることに注意して下さい。一旦、束縛したものの値を変えてしまうことを副作用と言います。実は s 自体はレコードへのポインタなので s 自体の内容は変更されていないのですが、s が指すものが変更されたことになります。

次にバリアントについて説明しましょう。バリアントは、C でいうところの enum 及び union を複合したものです。バリアントとパターンマッチは相性が良いため、非常に使いやすいです。

さて、バリアントは次のように宣言します。

# type t =
    A
  | B
  | C;;

type t = A | B | C

このとき、型 t は、A, B, C を要素に持つバリアント型となります。バリアントは、次の様に引数として渡すことも出来ます。

# let f x =
    match x with
      A -> 1
    | B -> 2
    | C -> 3;;

val f : t -> int = <fun>

ここで、x に A, B, C 以外のものを渡すと型推論によりエラーとなることに注意が必要です。ここに バリアントの大切さがあります。例えば C で enum を使って上記の様に A, B, C を持つ型 t を生成し、それを関数の引数に取る様にしても、型キャストによりあっさりと A, B, C 以外のものを渡すことが可能となります。ところが、OCaml は基本的にキャストがありません(あるにはあるのですが)。

これだけだと安全性以外はただの enum とそう変わらないのですが、バリアントは値を持つことができる所がにくいですね。

# type t =
    Node of int * int
  | Nil;;

type t = Node of int * int | Nil
# let f x =
    match x with
      Node (a, b) -> a + b
    | Nil -> 0
  ;;

val f : t -> int = <fun>

これにより、union を安全な形で使うことが可能となります。

例外

OCaml は例外をサポートします。これまで見てきた様に、全ての関数はなんらかの値を返します。では、例えば、int_of_string という C の atoi に相当する関数が不正な値を受け取った場合、何を返せば良いのでしょうか。C の関数がよくやるような -1 では、"-1" という値を int_of_string が受け取ったときに正しい値と区別することが出来ません。この場合、どの整数を返しても、正しいものと区別出来ません。しかし、型推論により、int_of_string は、整数しか返す事が出来ません。

すなわち、エラーであるか否かを知る機構が必要となるのですが、OCaml ではこれに例外を用います。

例外自体は Java や C++ で知っていると期待して、構文だけを説明します。

# exception Not_found;;

exception Not_found
# raise Not_found;;

Exception: Not_found.
# try
    raise Not_found
  with
    Not_found -> "Not_found"
  | _ -> "Other Exception";;

- : string = "Not_found"

まず、例外の定義は exception で行います。exception Foobar で、Foobar を例外と定義することができます。このとき、Foobar は値を取っても良いです。その場合は exception Foobar of string 等と定義します。例外は大文字で始めなければいけません。

例外の送出は、raise を用います。どこでも raise 出来るように、raise の型は exn -> 'a と定められています。

例外を受け取る場合は、try ... with 構文を用います。with 以下はパターンマッチで例外時の処理を続けます。

最初から定義されている例外に、Failure, Not_found, Invalid_argument があります。それらを簡単になげる関数 failwith や invalid_arg も存在します。

参照

レコード型の特殊な例として、参照型がはじめから存在します。参照型は、ref で生成します。

# let t = ref 0;;

val t : int ref = {contents = 0}

実は単なるレコードであることがお分かり頂けると思います。実際、t.contents で内容を取り出すことが出来ます。

内容を簡単に取り出すことができるように、演算子がいくつか定義されています。

# !t;;

- : int = 0
# t := 5;;

- : unit = ()
# t;;

- : int ref = {contents = 5}
# !t;;

- : int = 5

! は、参照の内容を取り出す演算子で、:= は、参照に代入する演算子です。

OCaml では、二項演算子を各自定義することが出来ます。二項演算子は、( ) をつけて定義します。例えば pervasives.ml を見れば、リストの連結を表す @ が、実際に次の様に定義されていることに驚きを感じるかも知れません。

let rec (@) l1 l2 =
  match l1 with
    [] -> l2
  | hd :: tl -> hd :: (tl @ l2)

新規 編集 添付