OCaml のモジュールについてのメモ その1
FUN の Introduction to Functional Programming in OCaml を受講し終えて,一番面白かったモジュールシステムについてイロイロ調べてみた.
ところが,思ったより独特の構文やらが多かったのでメモしておく.
OCaml
そもそも OCaml (Object Caml) とは. 1970年代に開発された ML (Meta Language) と言う関数型言語の方言の一つ Caml (Categorical Abstract Machine Language) に OOP の機能を加えた言語で,1996年に開発された(割と最近).
日本語の書籍はあるが,まぁまぁ古く,どれも 2007年代 (ocaml 3.09). そのため,ここ最近の新しい情報が載ってない.
にもかかわらず,モジュールシステムはここ最近にイロイロ新しくなったようだ.
- 3.12 (2011)
- First-class modules : 関数の引数や戻り値にしたりなどモジュールを普通の値のように扱える
module type of構築子の追加 : 作成したモジュールのモジュール型をうまく再利用するのを可能にする
- 4.00 (2012)
- First-class modules への変更 :
- モジュールをパック,アンパックをするときの型注釈を省けるようにした
- パターンマッチでアンパックできるようにした
- First-class modules への変更 :
そんなになかった.
まぁどっちにしても, First-class modules は大きな変更である.
モジュール
モジュールの役割は大きく分けて4つあるそうな
- プログラム及び名前空間の構造化
- インターフェースによる使用記述・部品化
- 抽象型・プライベート型(?) による隠蔽
- ファンクターによる抽象化
プログラム及び名前空間の構造化
部品に分けることをモジュール化というわけだし,1番はそのまんまだろう.
現に,OCaml ではファイル一枚を一つのモジュールにするらしい. つまり, hoge.ml というファイルは自動的に Hoge というモジュールになるらしい.
その,モジュールの中にはモジュールも定義でき,それをサブモジュールと言うらしい.
サブモジュールの定義の仕方と使い方は
# module Hello = struct
let message = "Hello"
let hello () = print_endline message
end ;;
module Hello : sig val message : string val hello : unit -> unit end
# Hello.message ;;
- : string = "Hello"
# Hello.hello () ;;
Hello
- : unit = ()インターフェースによる使用記述・部品化
2番のインターフェースはこんな感じに書ける.
# module Hello : sig
val hello : unit -> unit
end =
struct
let message = "Hello"
let hello () = print_endline message
end
module Hello : sig val hello : unit -> unit end
# Hello.hello () ;;
Hello
- : unit = ()
# Hello.message ;;
Characters 0-13:
Hello.message ;;
^^^^^^^^^^^^^
Error: Unbound value Hello.message上のようにインターフェースと実装を分けて書いたときインターフェースに書いた実装しか呼ぶことができない.
抽象型・プライベート型(?) による隠蔽
モジュールのインターフェースには型も定義できる.
# module Nat : sig
type t
val zero : t
val succ : t -> t
end =
struct
type t = int
let zero = 0
let succ n = n + 1
end
module Nat : sig type t val zero : t val succ : t -> t end
# Nat.zero ;;
- : Nat.t = <abstr>
# Nat.succ Nat.zero ;;
- : Nat.t = <abstr>
# Nat.succ 0 ;;
Characters 9-10:
Nat.succ 0 ;;
^
Error: This expression has type int but an expression was expected of type
Nat.t型 t の実装は完全に隠蔽されるため,実装的にはタダの int に過ぎないが,その互換性はなくなる. このときの型 t を抽象型とか存在型とか(プライベート型とか?)と言う.
また,module type を使えば,続けて書く必要はない
# module type Nat = sig
type t
val zero : t
val succ : t -> t
end
module type Nat = sig type t val zero : t val succ : t -> t end
# module Nat : Nat = struct
type t = int
let zero = 0
let succ n = n + 1
end
module Nat : Natまた,これを利用すると幽霊型(ファントム型)を使うことができる.
# module Money : sig
type 'a t constraint 'a = [< `Naked | `Taxed]
val make : float -> [`Naked] t
val get : 'a t -> float
val add : 'a t -> 'a t -> 'a t
val tax : [`Naked] t -> [`Taxed] t
end =
struct
type 'a t = float constraint 'a = [< `Naked | `Taxed]
let make x = x
let get x = x
let add x y = x +. y
let tax x = x *. 1.08
end
module Money :
sig
type 'a t constraint 'a = [< `Naked | `Taxed ]
val make : float -> [ `Naked ] t
val get : [< `Naked | `Taxed ] t -> float
val add : ([< `Naked | `Taxed ] as 'a) t -> 'a t -> 'a t
val tax : [ `Naked ] t -> [ `Taxed ] t
end
# Money.add (Money.make 3.0) (Money.make 4.0) |> Money.get ;;
- : float = 7.
# Money.add (Money.tax @@ Money.make 3.0) (Money.tax @@ Money.make 4.0) |> Money.get ;;
- : float = 7.5600000000000005
# Money.add (Money.tax @@ Money.make 3.0) (Money.make 4.0) |> Money.get ;;
Characters 40-56:
Money.add (Money.tax @@ Money.make 3.0) (Money.make 4.0) |> Money.get ;;
^^^^^^^^^^^^^^^^
Error: This expression has type [ `Naked ] Money.t
but an expression was expected of type [ `Taxed ] Money.t
These two variant types have no intersectionこれは,税込み (Taxed) かそうでないかを分けて型付けされたモジュールである 結果を見てわかるように,中身はどちらも同じ float だが,Taxed と Naked が違う場合は加算できない. ちなみに constraint とは全称型 'a に対して制約を与えている.
struct 内部での型の実装を与えているところ type 'a t = float で全称型 'a が右辺に出てこないような型のことを幽霊型と言う.
ファンクターによる抽象化
ファンクターを用いると,特定のモジュールをベースにして,モジュールを生成できる. (元ネタは圏論のファンクターなはずで,それは圏から圏への写像だと思ってる)
module type Nat_base = sig
type t
val zero : t
val succ : t -> t
end
module type Nat = sig
type t
include Nat_base with type t := t
val is_zero : t -> bool
val add : t -> t -> t
end
module Make_Nat (N : Nat_base) : Nat = struct
include N
let is_zero n = n = N.zero
let add n m =
let rec add' i x = if i = n then x else add' (N.succ i) (N.succ x)
in add' N.zero m
end
module NatI =
Make_Nat (struct
type t = int
let zero = 0
let succ x = x + 1
end)# NatI.add (NatI.succ NatI.zero) (NatI.succ NatI.zero) = NatI.succ (NatI.succ NatI.zero) ;;
- : bool = trueこのときの Make_Nat がファンクターである. これは,Set や Hash などの標準モジュールでも使われている.
第一級モジュール
参考
- OCaml の公式サイトのチュートリアル | モジュール – OCaml
- Real World OCaml の Web 版 | Chapter 9. Functors (なんかリンクが変だったので消した...)
- 入門OCaml ~プログラミング基礎と実践理解