OCaml に入門してみた。その2

Akihito Tanaka
all worldly things are transitory.
10 min readSep 25, 2018
“people riding camel on sand under cloudy sky at daytime” by Yeo Khee on Unsplash

引き続き OCaml の公式マニュアルを進めている。前回OCaml マニュアルの1.4章までだったので、今回は1.5章から。

1.5 Imperative features

ここまでの例は純粋なアプリカティブ・スタイルで書かれていたが、 OCaml は命令的な機能も備えている。それにはよくある whileforループ、 配列のようなミュータブルなデータストラクチャも含まれている。配列は [||]の間に ;で区切って要素を並べることで生成できる。もしくは、 Array.makeで生成し、あとで代入により値を埋めることができる。

例えば、次のように2つのベクターの和を求めることができる。

# let add_vect v1 v2 =
let len = min (Array.length v1) (Array.length v2) in
let res = Array.make 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.3;1.5; 4.|] [|0.5; 3.5; 3.|];;
- : float array = [|1.5; 3.8; 4.5|]

レコードのフィールドは、 レコード型を定義する際に 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.}

OCaml は変数の概念 (代入によって現在の値を変更する) がないが、標準ライブラリは !演算子を使って参照先の現在の値を取得することができ、 :=で値を代入できるミュータブルな間接参照を提供する。

# 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 a = [| 5; 3; 6; 2; 9; 1; 0 |];;
val a : int array = [|5; 3; 6; 2; 9; 1; 0|]
# insertion_sort a;;
- : unit = ()
# a;;
- : int array = [|0; 1; 2; 3; 5; 6; 9|]

参照は2回の関数呼び出しの間で現在の状態を維持する関数を書くのに役立つ。次の例の疑似乱数のジェネレータは最後に返した値を参照で保持する。

# 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>
# random ();;
- : int = 1345
# !current_rand;;
- : int = 1345

参照はミュータブルなフィールド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)

1.6 Exceptions

例外的な状態を知らせ、ハンドリングするために OCaml は例外を提供する。例外は exception を使って宣言し、 raise演算子で投げる。

# exception Empty_list;;
exception Empty_list
# let head l =
match l with
[] -> raise Empty_list
| hd :: tl -> hd;;
val head : 'a list -> 'a = <fun>
# head [1; 2];;
- : int = 1
# head [];;
Exception: Empty_list.

例外は、標準ライブラリでも、正常に関数を完了することができないようなケースを知らせるのに使われている。例えば、与えられたキーに関連するデータをペアのリストの中から返す List.assoc関数では、リストの中にキーが出てこなかった場合に Not_found 例外を投げる。

# List.assoc 1 [(0, "zero"); (1, "one")];;
- : string = "one"
# List.assoc 2 [(0, "zero"); (1, "one")];;
Exception: Not_found.

例外は try...with を使って捕捉することができる。

# let name_of_binary_digit digit =
try
List.assoc digit [0, "zero"; 1, "one"]
with Not_found ->
"not a binary digit";;
val name_of_binary_digit : int -> string = <fun>
# name_of_binary_digit 0;;
- : string = "zero"
# name_of_binary_digit (-1);;
- : string = "not a binary digit"

with の部分は match と同じシンタックスと動作で例外の値をパターンマッチングできる。なので、いくつかの例外を1つの try...with で捕捉することができる。

# let temporarily_set_reference ref newval funct =
let oldval = !ref in
try
ref := newval;
let res = funct () in
ref := oldval;
res
with x ->
ref := oldval;
raise x;;
val temporarily_set_reference : 'a ref -> 'a -> (unit -> 'b) -> 'b = <fun>

1.7 Symbolic processing of expressions

シンボリックプロセッシングのための典型的な OCaml の使用例を紹介する。

次の variant 型は、これから行おうとしている操作の式を表現している。

# type expression =
Const of float
| Var of string
| Sum of expression * expression (* e1 + e2 *)
| Diff of expression * expression (* e1 - e2 *)
| Prod of expression * expression (* e1 * e2 *)
| Quot of expression * expression (* e1 / e2 *)
;;
type expression =
Const of float
| Var of string
| Sum of expression * expression
| Diff of expression * expression
| Prod of expression * expression
| Quot of expression * expression

はじめに、与えられた変数名と値のマップで式を評価する関数を定義する。

# exception Unbound_variable_of_string;;
exception Unbound_variable_of_string
# let rec eval env exp =
match exp with
Const c -> c
| Var v ->
(try List.assoc v env with Not_found -> raise (Unbound_variable v))
| Sum(f, g) -> eval env f +. eval env g
| Diff(f, g) -> eval env f -. eval env g
| Prod(f, g) -> eval env f *. eval env g
| Quot(f, g) -> eval env f /. eval env g;;
val eval : (string * float) list -> expression -> float = <fun>
# eval [("x", 1.0); ("y", 3.14)] (Prod(Sum(Var "x", Const 2.0), Var "y"));;
- : float = 9.42

変数 dvに関する式の微分を定義する。

# let rec deriv exp dv =
match exp with
Const c -> Const 0.0
| Var v -> if v = dv then Const 1.0 else Const 0.0
| Sum(f, g) -> Sum(deriv f dv, deriv g dv)
| Diff(f, g) -> Diff(deriv f dv, deriv g dv)
| Prod(f, g) -> Sum(Prod(f, deriv g dv), Prod(deriv f dv, g))
| Quot(f, g) -> Quot(Diff(Prod(deriv f dv, g), Prod(f, deriv g dv)), Prod(g, g))
;;
# deriv (Quot(Const 1.0, Var "x")) "x";;
- : expression =
Quot (Diff (Prod (Const 0., Var "x"), Prod (Const 1., Const 1.)),
Prod (Var "x", Var "x"))

1.8 Pretty-printing と 1.9 Standalone OCaml programs は端折って1章は終わりにする。あまり遭遇したことが無い英単語がちらほら出てきてイマイチ理解しきれていない気もする。

--

--