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 () ;;
Hellounit = () - :
インターフェースによる使用記述・部品化
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 () ;;
Hellounit = ()
- :
# Hello.message ;;0-13:
Characters
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>
succ Nat.zero ;;
# Nat.
- : Nat.t = <abstr>
succ 0 ;;
# Nat.9-10:
Characters succ 0 ;;
Nat.
^type int but an expression was expected of type
Error: This expression has 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
3.0) (Money.make 4.0) |> Money.get ;;
# Money.add (Money.make float = 7.
- :
3.0) (Money.tax @@ Money.make 4.0) |> Money.get ;;
# Money.add (Money.tax @@ Money.make float = 7.5600000000000005
- :
3.0) (Money.make 4.0) |> Money.get ;;
# Money.add (Money.tax @@ Money.make 40-56:
Characters 3.0) (Money.make 4.0) |> Money.get ;;
Money.add (Money.tax @@ Money.make
^^^^^^^^^^^^^^^^type [ `Naked ] Money.t
Error: This expression has of type [ `Taxed ] Money.t
but an expression was expected 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 =
struct
Make_Nat (type t = int
let zero = 0
let succ x = x + 1
end)
succ NatI.zero) (NatI.succ NatI.zero) = NatI.succ (NatI.succ NatI.zero) ;;
# NatI.add (NatI.bool = true - :
このときの Make_Nat
がファンクターである. これは,Set
や Hash
などの標準モジュールでも使われている.
第一級モジュール
参考
- OCaml の公式サイトのチュートリアル | モジュール – OCaml
- Real World OCaml の Web 版 | Chapter 9. Functors (なんかリンクが変だったので消した...)
- 入門OCaml ~プログラミング基礎と実践理解