モジュール
プログラムの部品だが、OCaml では ストラクチャと呼ぶ。
OCamlのライブラリは全てモジュール(ストラクチャ)として提供される。
ファイル名がモジュール名
example.ml
というファイル名 → Example
というモジュール。
標準モジュール
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
しないのが普通。
(* 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
モジュール名は大文字で始める。
(* モジュール定義 *)
# 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
(* 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
抽象データ型
シグネチャ定義のとき、型定義(type t = ...
)の = ...
を省略すると
定義の詳細を隠蔽できる。
型情報と実装を隠蔽することで、その型に対する操作をモジュールを通じた操作に制限できる。
想定外の操作をできないようにする。
(* シグネチャ定義 *)
# 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
- 集合の要素を表す型
上記の「要素型のモジュール」をファンクターに「適用」して「その型を要素とす集合モジュール」を生成する。
(* ファンクター適用 *)
# 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 の簡易版のファンクタを定義する例
(* シグネチャの定義 *)
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
と記述されている- 仮引数の値が返り値の型に含まれている
- つまり, 与える引数の値(型ではない)に応じて返り値の型が変化する
- これを引数に依存しているので 依存型 という
- 仮引数の値が返り値の型に含まれている
-
(* 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 ファンクタ名 (引数名 : 入力シグネチャ式) : 返したいシグネチャ式 = モジュール式
- 返したいシグネチャ式を指定することで情報隠蔽可能
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
# 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
# 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
の部分- 仮引数名に依存しているためシグネチャを別名定義できない
- エラーになる
-
module MakeSet (Element : ELEMENT) :
(* 返したいシグネチャ式 *)
sig
type elt = Element.t
type t (* 抽象データ型 *)
end
=
struct ... end;;
with type構文を使う
(* 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(ネイティブコード)
まとめてコンパイル
$ ocamlc -o output.name src1.ml src2.ml ...
分割コンパイル
- 最後のリンク時の
cmo
ファイルは名前解決ができるように並べる必要がある - 非標準ライブラリ
- リンク時にファイル名を明示する必要あり
-
nums.cma
など
-
- リンク時にファイル名を明示する必要あり
-c
はリンクしないでオブジェクトファイルを出力するオプション
-I
で cmi
, cmo
をインクルードするディレクトリを指定可能
$ ocamlc -c mod1.ml
$ ocamlc -c mod2.ml
$ ocamlc -o output.name nums.cma mod1.cmo mod2.cmo
mliファイル
mli ファイルは対応する .ml
のシグネチャを記述したもの
コンパイル時は ocamlc
を使う
-
ocamlc
でmli
ファイルをコンパイル -
ocamlc -c
でml
ファイルをコンパイル -
ocamlc
でcmo
をリンク
$ ocamlc mod.mli # -c オプションは不要
$ ocamlc -c mod.ml
$ ocamlc -c mod2.ml
$ ocamlc -o a.out mod.cmo mod2.cmo
参考文献
非常に分かりやすく、OCamlを学ぶ際にオススメです。