第 12 章 語言擴展

5 一級模組

(在 OCaml 3.12 中引入;模式語法和封包類型推斷在 4.00 中引入;封包類型的結構比較在 4.02 中引入;從 4.05 開始需要較少的括號)

typexpr::= ...
 (modulepackage-type)
 
module-expr::= ...
 (valexpr [:package-type])
 
expr::= ...
 (modulemodule-expr [:package-type])
 
pattern::= ...
 (modulemodule-name [:package-type])
 
package-type::= modtype-path
 modtype-pathwithpackage-constraint { andpackage-constraint }
 
package-constraint::= typetypeconstr=typexpr
 

模組通常被認為是靜態元件。此擴展使得將模組打包為一級值成為可能,該值稍後可以動態解壓縮為模組。

表達式 ( module module-expr : package-type ) 將模組表達式 module-expr 表示的模組(結構或函式)轉換為封裝此模組的核心語言值。此核心語言值的類型為 ( module package-type )package-type 註解可以省略,如果它可以從上下文中推斷出來。

相反地,模組表達式 ( val expr : package-type ) 將核心語言表達式 expr 求值為一個值,該值必須具有類型 module package-type,並提取封裝在此值中的模組。同樣,如果已知 expr 的類型,則可以省略 package-type。如果模組表達式已經被括號包圍,就像函式的參數一樣,則不需要額外的括號:Map.Make(val key)

模式 ( module module-name : package-type ) 匹配類型為 package-type 的封包,並將其綁定到 module-name。它不允許在頂層 let 綁定中使用。同樣,如果它可以從封閉的模式中推斷出來,則可以省略 package-type

出現在 ( module package-type ) 類型表達式和帶註解形式中的 package-type 語法類別表示模組類型的子集。此子集由具名模組類型和有限形式的可選約束組成:只能指定非參數化類型。

為了類型檢查的目的(並且從 OCaml 4.02 開始),封包類型使用模組類型的結構比較進行比較。

一般來說,模組表達式 ( val expr : package-type ) 不能在函式的本體中使用,因為這可能會與應用函式結合使用時導致不健全。自 OCaml 4.02 以來,這在兩個方面得到了放寬:如果 package-type 不包含名義類型宣告( 使用正確的標識建立的類型),那麼這個表達式可以在任何地方使用,即使它包含此類類型,也可以在章節 ‍12.15 中描述的生成函式的主體內使用。它也可以在局部模組綁定 let module module-name = ( val expr1 : package-type ) in expr2 的上下文中任何地方使用。

基本範例

一級模組的典型用途是在執行時從簽名的多個實現中選擇。每個實現都是一個結構,我們可以將其封裝為一級模組,然後儲存在諸如雜湊表的資料結構中

type picture = … module type DEVICE = sig val draw : picture -> unit … end let devices : (string, (module DEVICE)) Hashtbl.t = Hashtbl.create 17 module SVG = structend let _ = Hashtbl.add devices "SVG" (module SVG : DEVICE) module PDF = structend let _ = Hashtbl.add devices "PDF" (module PDF : DEVICE)

然後,我們可以根據命令列引數選擇一個實現,例如

let parse_cmdline () = … module Device = (val (let device_name = parse_cmdline () in try Hashtbl.find devices device_name with Not_found -> Printf.eprintf "Unknown device %s\n" device_name; exit 2) : DEVICE)

或者,可以在函式內執行選擇

let draw_using_device device_name picture = let module Device = (val (Hashtbl.find devices device_name) : DEVICE) in Device.draw picture
進階範例

使用一級模組,可以將某些程式碼參數化到模組的實現上,而無需使用函式。

let sort (type s) (module Set : Set.S with type elt = s) l = Set.elements (List.fold_right Set.add l Set.empty)
val sort : (module Set.S with type elt = 's) -> 's list -> 's list = <fun>

要使用此函式,可以封裝 Set.Make 函式

let make_set (type s) cmp = let module S = Set.Make(struct type t = s let compare = cmp end) in (module S : Set.S with type elt = s)
val make_set : ('s -> 's -> int) -> (module Set.S with type elt = 's) = <fun>