今まではすべての例を純粋な関数型スタイルで書いてきましたが、Caml には手続き型の機能も備わっています。
配列などの変更可能なデータ構造や、おなじみの while
ループや for
ループです。配列は [|
と |]
のブラケットでリストのように記述するか、Array.create
関数で割り当てと初期化を行い、後で代入によって値を埋めることが出来ます。
例えば、次の関数は(浮動小数点数の配列で表された)ふたつのベクトルを要素ごとに足し算します。
#
let add_vect v1 v2 = let len = min (Array.length v1) (Array.length v2) in let res = Array.create len 0.0 in for i = 0 to len - 1 do res.(i) <- v1.(i) +. v2.(i) done; res;;
val add_vect : float array -> float array -> float array = <fun>
#
add_vect [| 1.0; 2.0 |] [| 3.0; 4.0 |];;
- : float array = [|4.; 6.|]
レコード型の定義に mutable
が宣言されているフィールドならば、そのフィールドも代入によって変更することが出来ます。
#
type mutable_point = { mutable x: float; mutable y: float };;
type mutable_point = { mutable x : float; mutable y : float; }
#
let translate p dx dy = p.x <- p.x +. dx; p.y <- p.y +. dy;;
val translate : mutable_point -> float -> float -> unit = <fun>
#
let mypoint = { x = 0.0; y = 0.0 };;
val mypoint : mutable_point = {x = 0.; y = 0.}
#
translate mypoint 1.0 2.0;;
- : unit = ()
#
mypoint;;
- : mutable_point = {x = 1.; y = 2.}
Caml にはもともと変数という概念がありません。ここで言う変数とは代入によって現在の値に変更できる識別子のことです(let
束縛は代入ではなく、新たなスコープで新しい識別子を導入しています)。しかし標準ライブラリに参照があります。これは変更可能なセル (または 1 要素の配列) で、 !
演算子で参照の現在保持している内容を取り出し、:=
で内容を代入します。これを使うと let
で束縛した参照で、擬似的に変数を実現できます。例として、配列の挿入ソートを示します。
#
let insertion_sort a = for i = 1 to Array.length a - 1 do let val_i = a.(i) in let j = ref i in while !j > 0 && val_i < a.(!j - 1) do a.(!j) <- a.(!j - 1); j := !j - 1 done; a.(!j) <- val_i done;;
val insertion_sort : 'a array -> unit = <fun>
次の呼び出しまでの間現在の状態を保持しなければならない関数を定義するときにも、参照が使えます。例として、最後に返した結果を参照に保存する疑似乱数生成器を示します。
#
let current_rand = ref 0;;
val current_rand : int ref = {contents = 0}
#
let random () = current_rand := !current_rand * 25713 + 1345; !current_rand;;
val random : unit -> int = <fun>
再度説明しますが、参照は特別なものではありません。以下のように 1 つのフィールドからなるレコードで実装されているのです。
#
type 'a ref = { mutable contents: 'a };;
type 'a ref = { mutable contents : 'a; }
#
let (!) r = r.contents;;
val ( ! ) : 'a ref -> 'a = <fun>
#
let (:=) r newval = r.contents <- newval;;
val ( := ) : 'a ref -> 'a -> unit = <fun>
時には、多相関数を多相性を保ったままデータ構造に格納する必要があることもあるでしょう。しかし、多相性は大域的にしか導入することができないため、ユーザーが型アノテーションを指定してやらなければ、これは実現できません。レコードのフィールドには多相型を明示することができます。
#
type idref = { mutable id: 'a. 'a -> 'a };;
type idref = { mutable id : 'a. 'a -> 'a; }
#
let r = {id = fun x -> x};;
val r : idref = {id = <fun>}
#
let g s = (s.id 1, s.id true);;
val g : idref -> int * bool = <fun>
#
r.id <- (fun x -> print_string "called id\n"; x);;
- : unit = ()
#
g r;;
called id called id - : int * bool = (1, true)