OCaml 入門その3  例外・ミュータブル・手続き型の処理

[ML Advent Calendar 2017](https://adventar.org/calendars/2142)

[その2](https://qiita.com/zenwerk/items/603bd383fe5c6b8cace3)

## 例外処理
ゼロ除算や、存在しないファイルを指定すると発生する。

```ocaml
# 1/0;;
Exception: Division_by_zero.
# open_in "";;
Exception: Sys_error ": No such file or directory".
```

### 例外を投げる(raise式)
**raise 例外**
**raise (例外 引数)**

例外に引数が必要な場合は `()` が必要。

```ocaml
# raise Not_found;;
Exception: Not_found.
# raise (Sys_error ": No such file or directory");;
Exception: Sys_error ": No such file or directory".
# raise (Sys_error ": 例外を投げます！");;
Exception:
Sys_error ": 139150130\146138\149129\146129129\153129".
```

```ocaml
# let rec fact n =
    if n < 0 then raise (Invalid_argument ": negative argument")
    else if n = 0 then 1 else n * fact (n-1);;
val fact : int -> int = <fun>
# fact 5;;
- : int = 120
# fact (-1);;
Exception: Invalid_argument ": negative argument".
```

### 例外処理(try with)

**try 式 with 例外1 -> 式1 | ...**

```ocaml
# try raise Not_found with
  | Not_found -> "not found !"
  | _ -> "unknown !";;
- : string = "not found !"

(* 上で定義した fact の例 *)
# try fact (-1) with
  | Invalid_argument _ -> 0
  | _ -> 9999;;
- : int = 0
```

### 例外の定義

例外とは、ヴァリアント型のコンストラクタで **例外コンストラクタ** と呼ばれる。
そのヴァリアントは **exn型** 

```ocaml
(* 例外のヴァリアント型の確認 *)
# Not_found;;
- : exn = Not_found
# raise;;
- : exn -> 'a = <fun>
```

例外定義とは、 `exn型` に新しいコンストラクタを追加すること。
`exn型` は特別で、後からコンストラクタを追加可能である。

**exception 例外名**

```ocaml
(* 例外定義 *)
# exception Hoge;;
exception Hoge
# exception Fuga of string;;
exception Fuga of string
# raise Hoge;;
Exception: Hoge.
# raise (Fuga "fuga!");;
Exception: Fuga "fuga!".
```

#### exn型について
`exn` もヴァリアント型なので、引数に渡したりすることも可能。

```ocaml
# exception Hoge;;
exception Hoge

(* exn型の配列 *)
# let exnlist = [Not_found; Hoge; (Invalid_argument "fuga")];;
val exnlist : exn list = [Not_found; Hoge; Invalid_argument "fuga"]

(* exn型を受け取る関数 *)
# let f = function
  | Hoge -> "hoge!"
  | x -> raise x;;
val f : exn -> string = <fun>
# f Hoge;;
- : string = "hoge!"
# f Not_found;;
Exception: Not_found.
```

## unit型
文字列を出力するプログラム。

```ocaml
# print_string "hoge\n";;
hoge
- : unit = ()
```

返り値は **unit型**
unit型の値は `()` という定数のみで、これを **ユニット値** と呼ぶ。

### unit型の使いみち
- `()` に対して行える演算はない
- 返り値自体に意味がない関数の返り値として使う
- 意味のある引数が要らない関数を定義するときに、引数として使う

```ocaml
# let const () = 777;;
val const : unit -> int = <fun>
# const ();;
- : int = 777
```

**操作が成功したか判断するための返り値として使う**

```ocaml
(* 
  () でパターンマッチする. 操作が成功すると unit型が返る.
  つまり, マッチが成功すれば, 操作が成功したことを表す.
*)
# let () = Bytes.set "Test" 1 'C';;
```

## ミュータブルなデータ構造

### 文字列の書き換え
**"文字列".[index] <- 'char'**

```ocaml
# let s = "life";;
val s : string = "life"
# s.[2] <- 'v';;
- : unit = ()
# s;;
- : string = "live"

(* String.set は非推奨 *)
# let f2 = "hoge";;
val f2 : string = "hoge"
# Bytes.set f2 0 'H';;
- : unit = ()
# f2;;
- : string = "Hoge"
```

この操作は`参照先`を操作する。

```ocaml
# let s = "hoge";;
val s : string = "hoge"
# let a = (s, s);;
val a : string * string = ("hoge", "hoge")
# Bytes.set f2 3 'E';;
- : unit = ()

(* 参照先が同じため, 両方が変更されている *)
# a;;
- : string * string = ("hogE", "hogE")
```

#### 物理的等価性
- **物理的等価性** => データのアドレスを比較したときの等しさ
	- `==`, `!=` を使う

- **構造的等価性** => 値として比較したときの等しさ
	- `=`, `<>` を使う

```ocaml
# let s1 = "hoge" and s2 = "hoge";;
val s1 : string = "hoge"
val s2 : string = "hoge"
(* 構造的等価性*)
# s1 = s2;;
- : bool = true
(* 物理的等価性 *)
# s1 == s2;;
- : bool = false
# s1 != s2;;
- : bool = true
```

### 書き換え可能レコード
- 書き換えレコード => **mutable**キーワードを使う
- レコードの書き換え => **レコード.フィールド名 <- 値**

```ocaml
# let ac = {name = "bob"; amount = 1000};;
val ac : account = {name = "bob"; amount = 1000}
# ac.amount <- 999;;
- : unit = ()
# ac;;
- : account = {name = "bob"; amount = 999}

(* 書き換え不可 *)
# let () = ac.name <- "Hoge";;
Error: The record field name is not mutable
(* でもこれは可能 *)
# ac.name.[0] <- 'B';;
- : unit = ()
# ac;;
- : account = {name = "Bob"; amount = 999}
```

### 参照の作成(ref)
- 参照の生成 => **ref関数**
- 参照先の取得 => **!参照**
- 参照先の書換 => **参照 := 値**

```ocaml
(* 参照の作成 *)
# let h = ref "Hoge" and f = ref "Fuga";;
val h : string ref = {contents = "Hoge"}
val f : string ref = {contents = "Fuga"}

(* 参照先の取得 *)
# let () = print_string (!h ^ !f ^ "\n");;
HogeFuga

(* 参照先の書換 *)
# h := !f;;
- : unit = ()
# let () = print_string (!h ^ !f ^ "\n");;
FugaFuga
```

#### 参照の参照(ref の ref)
C言語でいうダブルポインタのようなことも可能

```ocaml
(* 参照の生成 *)
# let r1 = ref 5 and r2 = ref 2;;
val r1 : int ref = {contents = 5}
val r2 : int ref = {contents = 2}
(* 参照の参照を生成 *)
# let rr1 = ref r1 and rr2 = ref r2;;
val rr1 : int ref ref = {contents = {contents = 5}}
val rr2 : int ref ref = {contents = {contents = 2}}
(* 参照先の操作 *)
# let () = !rr1 := !(!rr2);;
# (!r1, !r2);;
- : int * int = (2, 2)
```

### 配列
- **[| 値1; 値2; ... |]**
- 要素への参照 => **[|...|].(添字)**
- 要素の書き換え => **[|...|].(添字) <- 値**

配列の長さは生成時に固定される.
どの要素にも添え字で直接参照可能.

## 手続き型の制御構造
OCaml の仕様として引数の評価順は未定義。
そのため、手続き型言語のような実行順序を制御するための構文が用意されている。

### 逐次実行
**式1; 式2; ... 式n**

これは以下と同義

```ocaml
let _ = 式1 in
let _ = 式2 in
.
.
式n
```

最後の `式n` が全体の返り値となる.

#### begin と end
`(式; ... 式n)` の代わりに **begin 式1; ... 式n end** と記述可能
こちらの方が好まれるようだ。

#### ignore
文相当の式が `unit型` 以外を返すと警告が出る。
その警告を無視する関数が **ignore**

```ocaml
(* 返り値を　0 (int) にする *)
# let print_hello () = print_string "Hello, "; 0;;
val print_hello : unit -> int = <fun>

(* print_hello に警告が出る *)
# print_hello (); print_string "World\n";;
Warning 10: this expression should have type unit.
Hello, World
- : unit = ()

(* ignore すると警告が出ない *)
# ignore (print_hello ()); print_string "World\n";;
Hello, World
- : unit = ()
```

##### ; は区切り
`;` は終端ではなく、区切り記号として使う。
よって、`式n`の最後を `式n;` のようにセミコロンを付けると余計なエラーが出ることが多いので注意。

### 条件分岐
`if then` の `then` 節の返り値が `()` のときは `else` が省略できる。

**if 式1 then 式2** => **if 式1 then 式2 else ()** と同義
式1 が成立しなければ何もしない、という意味。

### ループ
for と while がある

#### for
for => **for variable = start_value to end_value do 式 done** 
または
**for variable = start_value downto end_value do 式 done**

```ocaml
(* forループ *)
# for i = 1 to 10 do begin print_int i; () end done;;
12345678910- : unit = ()
# for i = 10 downto 1 do begin print_int i; () end done;;
10987654321- : unit = ()
```

#### while
**while 条件 do 式 done**

while はあまり使われないとのこと。

```ocaml
let quit_loop = ref false in
  while not !quit_loop do
  print_string "Have you had enough yet? (y/n) ";
  let str = read_line () in
  if str.[0] = 'y' then
      quit_loop := true
  done;;
Have you had enough yet? (y/n) n
Have you had enough yet? (y/n) y
- : unit = ()
```

## 参考文献
![](http://image.gihyo.co.jp/assets/images/cover/2007/9784774132648.jpg)

[プログラミング in OCaml](http://gihyo.jp/book/2007/978-4-7741-3264-8)

非常に分かりやすく、OCamlを学ぶ際にオススメです。