OCamlチュートリアルを進めた話(その2)

モジュールの話。

最新コンパイラ構成技法を読むためにMLの勉強。

本ではSMLだけどOCamlやる。

OCamlチュートリアル - OCamlを進めてく。

内容は上のサイトそのまんまです。あくまで備忘録。

今回はモジュールを進めてく(たぶん以降は1ページ1記事スタイル)。

モジュール

  • モジュールはファイルシステムに似てて、サブモジュールとか作れる
  • つまり、モジュールは関数、型、サブモジュールなどを提供する
  • amodule.mlは自動的にAmoduleモジュールとして定義される
  • コンパイルはファイルごとに行ったあとリンク(?)する
ocamlopt -c amodule.ml
ocamlopt -c bmodule.ml
ocamlopt -o hello amodule.cmx bmodule.cmx
  • どうでもいいが;;はさけられる傾向にあるらしい
open Amodule;;
hello ();;
open Amodule
let () =
  hello ()
  • と書かれる
  • あともういっこ例がありました
open Printf
let my_data = [ "a"; "beautiful"; "day" ]
let () = List.iter (fun s -> printf "%s\n") my_data;;
  • ラムダ式fun arg1 arg2 ... argn -> expressionなんすね

インターフェースとシグネチャ

  • .mliファイルを使ってモジュールインターフェース(シグネチャ)を定義する
  • 例えば、hello.ml
let message = "Hello"
let hello () = print_endline message
  • で、hello.mli
val hello : unit -> unit
(** Displays a greeting message. *)
  • みたいな。
  • .mliファイルのドキュメントはocamldocがサポートしてるフォーマットで書くといいらしい

抽象型

  • type date = { day : int; month : int; year : int }.mliファイルに書き出す方法は4つ
      1. そもそもシグネチャに書かない
      1. 型定義をそのままコピペ
      1. 型を抽象化して名前だけ与える: type date
      1. レコードを読み出し専用にする: type date = private { ... }
  • 3.、4.のときはフィールドに直接アクセスできないので
  • 例があるよ!
type date
val create : ?days:int -> ?months:int -> ?years:int -> unit -> date
val sub : date -> date -> date
val years : date -> float
  • 何書いてるか分からないけどね。
  • まず?days:intが分からないし、レコード作成して返す関数なら素直にint -> int -> int -> dateってなるんじゃね?って思った
  • あれか、型エイリアスとかか?monthintだけど1 ~ 12だからみたいな。
  • まああとで分かるでしょ。飛ばそ。

サブモジュール

  • 以下example.ml
module Hello = struct
  let message = "Hello"
  let hello () = print_endline message
end
let goodbye () = print_endline "Goodbye"
let hello_goodbye () =
  Hello.hello ();
  goodbye ()
  • 以下別ファイルからのアクセス
let () =
  Example.Hello.hello ();
  Example.goodbye ()
  • 以上。
  • サブモジュールのインターフェースの制限にはモジュール型を使う
module Hello : sig
 val hello : unit -> unit
end = 
struct
  let message = "Hello"
  let hello () = print_endline message
end
       
(* これで、Hello.message はどこからもアクセスできない *)
let goodbye () = print_endline "Goodbye"
let hello_goodbye () =
  Hello.hello ();
  goodbye ()
  • 一緒くたに書いたけど、普通分けて書くらしい
module type Hello_type = sig
 val hello : unit -> unit
end
   
module Hello : Hello_type = struct
  ...
end
  • 名前付きモジュール型であるHello_typeを定義してからサブモジュール定義に利用してる形
  • なんかこのあとのファンクタで役立つらしいよ

ファンクタ

  • 取り敢えず既存のファンクタの使い方見る
module Int_set = Set.Make (struct
                             type t = int
                             let compare = compare
                           end);;
module String_set = Set.Make (String);;
  • ジェネリクスとかテンプレートみたいなもん?
  • まあ返ってくんのはモジュールだけど
  • モジュールがJavaとかC++でいうクラスの役割もしてるんだしまあ当然っちゃ当然
  • つまりファンクタは受け取った型に応じたモジュールを返すもの?
module F (X : X_type) = struct
 ...
end
  • と型を引数に取って定義すること以外モジュールと同じ
module F (X : X_type) : Y_type =
struct
  ...
end
  • とシグネチャを付けてインターフェースを制限することもできる
  • .mliファイルにmodule F (X : X_type) : Y_typeを書き込む方法でもいい
  • 標準ライブラリのset.mlとかmap.mlとかがいい例らしい

既存モジュールの拡張

module List = struct
  include List
  let rec optmap f = function
    | [] -> []
    | hd :: tl ->
       match f hd with
       | None -> optmap f tl
       | Some x -> x :: optmap f tl
end;;
  • 特に言うこともない
  • 他のモジュールからインポートする際は、そのまま読み込めば標準のListモジュールをオーバーライドできる