OCaml 入門その4  モジュール・ファンクタ

[その3](https://qiita.com/zenwerk/items/bfc1978718b5da3f463b)

## モジュール
プログラムの部品だが、OCaml では **ストラクチャ**と呼ぶ。
OCamlのライブラリは全てモジュール（ストラクチャ）として提供される。

### ファイル名がモジュール名
`example.ml` というファイル名　→　`Example` というモジュール。

### 標準モジュール
OCaml組み込みのモジュール郡

```ocaml
(* リスト *)
# List.length [1; 2; 3];;
- : int = 3
# let q = Queue.create ();;
val q : '_a Queue.t = <abstr>

(* キュー *)
# Queue.add "first" q;;
- : unit = ()
# Queue.take q;;
- : string = "first"
# Queue.take q;;
Exception: Queue.Empty.

(* 配列 *)
# Array.make 3 'a';;
- : char array = [|'a'; 'a'; 'a'|]

(* 標準出力 *)
# Printf.printf "%d, %x, %s\n" 10 255 "hoge";;
10, ff, hoge
- : unit = ()
```

### open宣言
**open モジュール名** と宣言することでモジュール名を省略可能。

Python でいう `from hoge import *` に近い。

現在のソースに`open`したモジュールの名前空間が展開されるため、混乱しない時のみ`open`するようにする。
コンテナ系のモジュールは関数名が重なることが多いため、`open`しないのが普通。

```ocaml
(* open宣言 *)
# open List;;
# length [1; 2; 3];;
- : int = 3

(* 関数名を上書きする *)
# let length () = "overload!";;
val length : unit -> string = <fun>
# length ();;
- : string = "overload!"

(* モジュール名を指定すれば呼び出せる *)
# List.length [1; 2; 3];;
- : int = 3
```

ちなみに OCaml では起動時に `Pervasives` というモジュールが `open` されている。
`abs` や `open_in` などの関数はこのモジュールに属する。

### モジュール定義
**module モジュール名 = struct いろいろ定義... end**
モジュール名は大文字で始める。

```ocaml
(* モジュール定義 *)
# module Hello = struct
    let message = "Hello"
    let hello () = print_endline message
  end;;
module Hello : sig val message : string val hello : unit -> unit end
(* 呼び出し *)
# Hello.hello ();;
Hello
- : unit = ()
```

### シグネチャ
- **シグネチャ**
  - **sig ... end** で囲まれた部分
  - モジュール全体の型（のようなもの）
    - モジュールのI/Fを表す (アクセス可能なモジュールの要素を定義できる)

#### mli ファイル
`Hoge`モジュールのシグネチャは `hoge.mli` ファイルで定義可能。
ファイル内にシグネチャを記述する。

### シグネチャの定義
定義　→　**module type シグネチャ名 = sig ... end**
適用　→　**module モジュール名 : シグネチャ名 = モジュール名 または struct ... end**

```ocaml
(* message 要素にアクセスできる *)
# Hello.message;;
- : string = "Hello"

(* messageが定義されていないシグネチャを定義する *)
# module type Hello_Sig =
    sig
      val hello: unit -> unit
    end;;
module type Hello_Sig = sig val hello : unit -> unit end

(* シグネチャを指定してモジュールを与える *)
# module Hello2 : Hello_Sig = Hello;;
module Hello2 : Hello_Sig

(* シグネチャにないため, message要素はアクセス不可 *)
# Hello2.hello ();;
Hello
- : unit = ()
# Hello2.message;;
Error: Unbound value Hello2.message

(* モジュールを直接定義するバージョン *)
# module Hello3 : Hello_Sig = struct
    let hello () = print_endline "Hello3!"
  end;;
module Hello3 : Hello_Sig
# Hello3.hello ();;
Hello3!
- : unit = ()
# Hello3.message;;
Error: Unbound value Hello3.message
```

### 抽象データ型
**<abstr>**

シグネチャ定義のとき、型定義（`type t = ...`）の `= ...` を省略すると
定義の詳細を隠蔽できる。

型情報と実装を隠蔽することで、その型に対する操作をモジュールを通じた操作に制限できる。
想定外の操作をできないようにする。

```ocaml
(* シグネチャ定義 *)
# module type AbstTypeSig = sig
    type t (* 抽象データ型 *)
    val get_t : int -> t
    val print : t -> unit
  end;;
module type AbstTypeSig =
  sig type t val get_t : int -> t val print : t -> unit end

(* モジュール定義 *)
# module AbstTypeInt : AbstTypeSig = struct
    type t = int
    let get_t i = i
    let print t = print_int t
  end;;
module AbstTypeInt : AbstTypeSig

(* 返り値が抽象データ型 <abstr> *)
# let t = AbstTypeInt.get_t 0;;
val t : AbstTypeInt.t = <abstr>
# AbstTypeInt.print t;;
0- : unit = ()

(* 
  抽象データ型は外部から扱えない
  AbstTypeInt.t は実質 int だが, 抽象データ型で隠蔽されているため
  print_int の引数に渡してもエラーになる
*)
# let () = print_int t;;
Error: This expression has type AbstTypeInt.t
       but an expression was expected of type int
```

## ファンクター
パラメータを適用して動的にモジュールを生成する**関数**。
一部だけ異なるモジュールを何度も定義する面倒をなくす。

### ファンクターを使う
ファンクター適用　→　**ファンクター名 (モジュール式)**

※ ファンクター適用もモジュール式

#### 集合を扱う `Set` モジュールの例　
標準モジュールの `Set` や `Queue` がファンクターを利用している。
扱いたい集合の要素に関して、以下のモジュールを定義する

- 集合の要素のモジュールを定義
  - 集合の要素を表す型`t`
  - 要素型tの大小を比較する関数 `compare: t -> t  -> int`

上記の「要素型のモジュール」をファンクターに「適用」して「その型を要素とす集合モジュール」を生成する。

```ocaml
(* ファンクター適用 *)
# module IntSet = Set.Make (struct
    type t = int
    let compare i j = i - j
  end);;
module IntSet :
  sig
    type elt = int (* 要素型として扱いたかった要素 elt = int *)
    type t         (* 集合を表す型は IntSet.t は抽象データ型 *)
    val empty : t
    val is_empty : t -> bool
    val mem : elt -> t -> bool
    val add : elt -> t -> t
    val singleton : elt -> t
    val remove : elt -> t -> t
    val union : t -> t -> t
    val inter : t -> t -> t
    val diff : t -> t -> t
    val compare : t -> t -> int
    val equal : t -> t -> bool
    val subset : t -> t -> bool
    val iter : (elt -> unit) -> t -> unit
    val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a
    val for_all : (elt -> bool) -> t -> bool
    val exists : (elt -> bool) -> t -> bool
    val filter : (elt -> bool) -> t -> t
    val partition : (elt -> bool) -> t -> t * t
    val cardinal : t -> int
    val elements : t -> elt list
    val min_elt : t -> elt
    val max_elt : t -> elt
    val choose : t -> elt
    val split : elt -> t -> t * bool * t
    val find : elt -> t -> elt
    val of_list : elt list -> t
  end

(* ファンクターから生成されたモジュールを利用する *)
# open IntSet;;
# let s1 = add 2 (add 1 empty)
  and s2 = add 1 (add 3 empty);;
val s1 : IntSet.t = <abstr>
val s2 : IntSet.t = <abstr>
# mem 1 s1;;
- : bool = true
```

### ファンクターの定義
**module ファンクタ名 (引数名 : シグネチャ式) = モジュール式**

は以下の糖衣構文

**module ファンクタ名 = functor(引数名 : シグネチャ式) -> モジュール式**

- Set.Make の簡易版のファンクタを定義する例
 
```ocaml
(* シグネチャの定義 *)
module type ELEMENT = sig
  type t
  val compare: t -> t -> int
end

(* ファンクタの定義 *)
module MakeSet (Element : ELEMENT) =
  struct
    type elt = Element.t
    type t = elt list

    let empty = []

    let mem x set = List.exists (fun y -> Element.compare x y = 0) set

    let rec add elt = function
    | [] -> [elt]
    | (x :: rest as s) ->
        match Element.compare elt x with
        | 0 -> s
        | r when r < 0 -> elt :: s
        | _ -> x :: (add elt rest)

    let rec elements s = s
  end;;
```

#### 依存型
- 上記のファンクタの返り値のシグネチャは以下
  - `functor (Element : ELEMENT) ->` のシグネチャ `sig ... end` の中を見ると `type elt = Element.t` と記述されている
    - 仮引数の値が返り値の型に含まれている
      - つまり, 与える引数の値（型ではない）に応じて返り値の型が変化する
      - これを引数に依存しているので **依存型** という

```ocaml
(* module type のシグネチャ *)
module type ELEMENT = sig type t val compare : t -> t -> int end
(* ファンクタのシグネチャ *)
module MakeSet :
  functor (Element : ELEMENT) ->
    sig
      type elt = Element.t (* ここが依存型 *)
      type t = elt list
      val empty : 'a list
      val mem : Element.t -> Element.t list -> bool
      val add : Element.t -> Element.t list -> Element.t list
      val elements : 'a -> 'a
    end
```

### ファンクタの情報隠蔽
通常のモジュールと同様に、ファンクターの返り値のシグネチャを制限し内部実装を隠蔽可能。

- **module ファンクタ名 (引数名 : 入力シグネチャ式) : 返したいシグネチャ式 = モジュール式**
  - 返したいシグネチャ式を指定することで情報隠蔽可能

```ocaml
module MakeSet (Element : ELEMENT) :
  (* 返したいシグネチャ式 *)
  sig
    type elt = Element.t
    type t (* 抽象データ型 *)
    val mem : elt -> t -> bool
    val add : elt -> t -> t
    val elements : t -> elt list
  end
  =
  (* モジュール式 *)
  struct
    type elt = Element.t
    type t = elt list

    let empty = []

    let mem x set = List.exists (fun y -> Element.compare x y = 0) set

    let rec add elt = function
    | [] -> [elt]
    | (x :: rest as s) ->
        match Element.compare elt x with
        | 0 -> s
        | r when r < 0 -> elt :: s
        | _ -> x :: (add elt rest)

    let rec elements s = s
  end;;
  
(* ファンクター式の返り値 *)
module MakeSet :
  functor (Element : ELEMENT) ->
    sig
      type elt = Element.t
      type t
      val empty : t
      val mem : elt -> t -> bool
      val add : elt -> t -> t
      val elements : t -> elt list
    end
```

上記のファンクター適用式の返り値のシグネチャ式を比較すると

- Before
 
```ocaml
# module StringSet = MakeSet(String);;
module StringSet :
  sig
    type elt = String.t
    type t = elt list
    val empty : 'a list
    val mem : String.t -> String.t list -> bool
    val add : String.t -> String.t list -> String.t list
    val elements : 'a -> 'a
  end
```

- After

```ocaml


# module IntSet = MakeSet(struct
    type t = int
    let compare i j = i - j
  end);;
module IntSet :
  sig
    type elt = int
    type t (* 抽象データ型 *)
    val empty : t
    val mem : elt -> t -> bool
    val add : elt -> t -> t
    val elements : t -> elt list
  end

# Open IntSet;;
# let s1 = add 1 (add 2 empty)
  and s2 = add 3 (add 4 empty);;
val s1 : IntSet.t = <abstr> (* 抽象データ型 *)
val s2 : IntSet.t = <abstr>

(* 文字列のとき *)
# module StringSet = MakeSet(String);;
module StringSet :
  sig
    type elt = String.t
    type t = MakeSet(String).t (* 何か実装が隠されていない？ *)
    val empty : t
    val mem : elt -> t -> bool
    val add : elt -> t -> t
    val elements : t -> elt list
  end

# open StringSet;;
# let s1 = add "a" (add "b" empty)
  and s2 = add "c" (add "d" empty);;
val s1 : StringSet.t = <abstr> (* 抽象データ型にはなっている *)
val s2 : StringSet.t = <abstr>
```

### シグネチャの分解・定義 (with type)
**シグネチャ式 with type 型名 = 型定義 and type ...**

- ファンクターの返したいシグネチャ定義を `sig ... end` で直接指定せず、名前で与えたい。
  - `type elt = Element.t` の部分
    - 仮引数名に依存しているためシグネチャを別名定義できない
    - エラーになる

```ocaml
module MakeSet (Element : ELEMENT) :
  (* 返したいシグネチャ式 *)
  sig
    type elt = Element.t
    type t (* 抽象データ型 *)
  end
  =
  struct ... end;;
```

#### with type構文を使う

```ocaml
(* with type で置換したい型定義 elt *)
# module type ElementWithType =
    sig
      type elt
      type t
      val empty : t
      val mem : elt -> t -> bool
      val add : elt -> t -> t
      val elements : t -> elt list
    end;;

(* with type で定義を与える *)
# module type E2 = ElementWithType with type elt = int;;
module type E2 =
  sig
    type elt = int
    type t
    val empty : t
    val mem : elt -> t -> bool
    val add : elt -> t -> t
    val elements : t -> elt list
  end
```

## バッチコンパイラと分割コンパイル
ファイル単位で書き出すコンパイラ => **バッチコンパイラ**
ファイル単位でオブジェクトファイルを作成しリンク => **分割コンパイル**

ocaml のバッチコンパイラ　→　`ocamlc(バイトコード)`, `ocamlopt(ネイティブコード)`

### まとめてコンパイル

```sh
$ ocamlc -o output.name src1.ml src2.ml ...
```

### 分割コンパイル
- 最後のリンク時の `cmo` ファイルは名前解決ができるように並べる必要がある
- 非標準ライブラリ
  - リンク時にファイル名を明示する必要あり
    - `nums.cma` など
    
`-c` はリンクしないでオブジェクトファイルを出力するオプション
`-I` で `cmi`, `cmo` をインクルードするディレクトリを指定可能

```sh
$ ocamlc -c mod1.ml
$ ocamlc -c mod2.ml
$ ocamlc -o output.name nums.cma mod1.cmo mod2.cmo
```
### mliファイル
mli ファイルは対応する `.ml` のシグネチャを記述したもの
コンパイル時は `ocamlc` を使う

1. `ocamlc` で `mli` ファイルをコンパイル
2. `ocamlc -c` で `ml` ファイルをコンパイル
3. `ocamlc` で `cmo` をリンク

```sh
$ ocamlc mod.mli  # -c オプションは不要
$ ocamlc -c mod.ml
$ ocamlc -c mod2.ml
$ ocamlc -o a.out mod.cmo mod2.cmo
```

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

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

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