OCaml モジュールとシグネチャ
ソースファイル .ml
をコンパイルするとオブジェクトファイル .cmo
や .cmx
.o
と同時にインターフェイスファイル .cmi
が生成される。
この .cmi
について調べてみた。理解してしまえば何ということはないのだけれど、少し時間がかかった。
.mli
をコンパイルすると.cmi
が生成される。.mli
がない場合は.ml
から.cmi
が生成される。
では .mli
とは何だろう。
ではモジュールとは、シグネチャとは何だろう。
モジュールとは
詳細な解説は以下の記事、資料を読むとよさそう。
ただ、一度に全部を理解しようとするのはかなり困難だったので、ひとまずは「関連する処理やデータなどのまとまり」くらいの理解でよいと思う。Python や Go でいうパッケージと同じようなものだと考えればよいだろう。
OCaml では一つのファイルを一つのモジュールとするそう。前回書いた、階乗を求める処理でもモジュールとして扱っていた。
fact.ml
は Fact
モジュールとなる。
(* fact.ml *) let rec fact n = if n = 0 then 1 else n * fact (n - 1)
利用する側からは open
で読み込む。
(* main.ml *) open Fact let () = print_int (fact 10); print_newline()
もしくは、モジュール名.関数
の形でドット .
をつけてモジュール内の関数を利用する。
(* main.ml *) let () = print_int (Fact.fact 10); print_newline()
モジュールに別名をつけて読み込む
Python で as
で別名をつけて import
するのと同じように
import numpy as np
以下の書き方で別名をつけて open
することができる。
module F = Fact let () = print_int (F.fact 10); print_newline()
- この場合
open
というキーワードは使わない。 - 別名
F
の部分は大文字で始める必要あり。
シグネチャとは
モジュールについては他の言語でも似たような仕組みはあるので、何となくではあるけれどわかった。次はシグネチャである。
OCaml におけるシグネチャは、モジュールの中身に対するアクセスの可否を制御する仕組みで、個々の関数には private とか public とかつけずに別途まとめて管理できるようになっている。
今まで触ったことのある言語(Python, Go, JavaScript あたり)ではこれに該当するものはなかったと思われ、新鮮に感じた。言語によっては、関数の仮引数や戻り値の型、アクセス修飾子などのことを指してシグネチャと呼ぶ場合があるみたいだけれど。
Python だと名前の頭にアンダースコア _
や __
をつけることで private 扱いになり、Go だと名前の頭文字が大文字なら外部からアクセス可能で小文字だとアクセス不可、という仕様になっている。
- 9. クラス 9.6 プライベート変数 — Python 3.6.1 ドキュメント
- Pythonを書き始める前に見るべきTips - Qiita
- A Tour of Go - Exported names
で、OCaml の場合。
Fact
モジュールを少し拡張して試してみる。階乗 n!
のバリエーションとして多重階乗というものがあるらしい。二重階乗 n!!
であれば一つ飛ばしで積をとる。
5!! = 5 * 3 * 1 10!! = 10 * 8 * 6 * 4 * 2
この多重階乗を求める関数 multifact
を書いてみた。
(* fact.ml *) let rec fact n = if n = 0 then 1 else n * fact (n - 1) let rec multifact x n = match n with | 0 -> 1 | 1 -> 1 | _ -> if x > n then n else n * multifact x (n - x) let doublefact = multifact 2
モジュールを利用する側のコード。
(* main.ml *) open Fact let () = print_int (fact 10); print_newline (); print_int (multifact 2 10); print_newline (); print_int (doublefact 10); print_newline (); ;;
実行結果はこうなる。
# コンパイルして $ ocamlc -o fact10 fact.ml main.ml # 実行する $ ./fact10 3628800 # fact 10 3840 # multifact 2 10 3840 # doublefact 10
シグネチャを *.mli
に書く
次に Fact
モジュールのシグネチャを fact.mli
に書く。公開するものだけを書き、シグネチャに書かれていないものは外部からアクセスできなくなる。
試しに multifact
は非公開としてみる。
(* fact.mli *) val fact : int -> int val doublefact : int -> int
このシグネチャの書式はコンパイラに -i
オプションをつけて当該ファイルを渡すと確認できる。-i
オプションのみの場合は実際のコンパイルはしない(.cmo
ファイルは生成されない)。
$ ocamlc -i fact.ml val fact : int -> int val multifact : int -> int -> int val doublefact : int -> int
*.mli
にリダイレクトして、公開したくないものを削除すればよい。
$ ocamlc -i fact.ml > fact.mli
ファイル *.mli
が存在する場合は、*.ml
よりも先にコンパイルしないとエラーになる。
$ ocamlc -c fact.ml File "fact.ml", line 1: Error: Could not find the .cmi file for interface fact.mli.
fact.mli
をコンパイルして、
$ ocamlc -c fact.mli # fact.cmi が生成される
fact.ml
をコンパイルして、
$ ocamlc -c fact.ml # fact.cmo が生成される
main.ml
をコンパイルしようとすると、エラー!
$ ocamlc -c main.ml File "main.ml", line 4, characters 13-22: Error: Unbound value multifact
ふむふむ。
それでは修正してみよう。main.ml
から multifact
を呼ぼうとしている箇所をコメントアウトして、
(* main.ml *) open Fact let () = print_int (fact 10); print_newline (); (* print_int (multifact 2 10); print_newline (); *) print_int (doublefact 10); print_newline (); ;;
コンパイルすると、今度は無事通った。
$ ocamlc -c main.ml # main.cmo が生成される $ ls . .. fact.cmi fact.cmo fact.ml fact.mli main.cmi main.cmo main.ml
これで必要なファイルが揃ったので、以下のように -o
オプションで実行ファイルを出力する。
$ ocamlc -o fact10 fact.cmo main.cmo $ ./fact10 3628800 3840
あるいは、以下のようにして最初から実行ファイルを出力することも可能。この場合もコンパイラには *.mli
を先に渡す必要あり。
$ ocamlc -o fact10 fact.mli fact.ml main.ml
シグネチャを書かない場合は全て公開される
先のエントリで見たようにシグネチャを書かなくても *.ml
から *.cmi
が生成されていた。この場合は、モジュールの中身は全て公開されることになる。
まぁ OCaml プログラマが意図的にシグネチャを書かないというケースはなさそう(想像)だから、気にしなくていいんじゃないかな。