第 12 章 語言擴展

7 在簽章內替換

7.1 破壞性替換

(OCaml 3.12 引入,4.06 泛化)

mod-constraint::= ...
 type [type-params] typeconstr-name:=typexpr
 modulemodule-path:=extended-module-path

「破壞性」替換(with ... := ...)的行為本質上與一般的簽章約束(with ... = ...)類似,但它還會從簽章中移除重新定義的型別或模組。

在 OCaml 4.06 之前,存在一些限制:只能在最外層(而非子模組內)移除型別和模組,且在 with type 的情況下,定義必須是另一個具有相同型別參數的型別建構子。

破壞性替換的一個自然應用是合併兩個共享型別名稱的簽章。

module type Printable = sig type t val print : Format.formatter -> t -> unit end module type Comparable = sig type t val compare : t -> t -> int end module type PrintableComparable = sig include Printable include Comparable with type t := t end

也可以使用它來完全移除一個欄位

module type S = Comparable with type t := int
module type S = sig val compare : int -> int -> int end

或重新命名一個

module type S = sig type u include Comparable with type t := u end
module type S = sig type u val compare : u -> u -> int end

請注意,您也可以移除顯式型別,透過使用相同的型別進行替換。

module type ComparableInt = Comparable with type t = int ;;
module type ComparableInt = sig type t = int val compare : t -> t -> int end
module type CompareInt = ComparableInt with type t := int
module type CompareInt = sig val compare : int -> int -> int end

7.2 局部替換宣告

(OCaml 4.08 引入,模組型別替換於 4.13 引入)

specification::= ...
 typetype-subst { andtype-subst }
 modulemodule-name:=extended-module-path
 moduletypemodule-name:=module-type
 
type-subst::=[type-params] typeconstr-name:=typexpr { type-constraint }

局部替換的行為類似於破壞性替換(with ... := ...),但它們不是在事後應用於整個簽章,而是在簽章的規格設定期間引入,並將應用於之後的所有項目。

這提供了一種在定義簽章時為型別和模組引入局部名稱的便捷方式

module type S = sig type t module Sub : sig type outer := t type t val to_outer : t -> outer end end
module type S = sig type t module Sub : sig type t val to_outer : t -> t/2 end end

請注意,與型別宣告不同,型別替換宣告不是遞迴的,因此會拒絕如下替換

# module type S = sig type 'a poly_list := [ `Cons of 'a * 'a poly_list | `Nil ] end ;;
Error: Unbound type constructor poly_list

局部替換也可以用於為函子應用所引入的型別或模組型別提供局部名稱

# module type F = sig type set := Set.Make(Int).t module type Type = sig type t end module Nest : Type -> sig module type T = Type end module type T := Nest(Int).T val set: set val m : (module T) end;;
module type F = sig module type Type = sig type t end module Nest : Type -> sig module type T = Type end val set : Set.Make(Int).t val m : (module Nest(Int).T) end

局部模組型別替換與模組型別替換受到相同的限制,請參閱第 12.7.3 節。

7.3 模組型別替換

(OCaml 4.13 引入)

mod-constraint::= ...
 moduletypemodtype-path=module-type
 moduletypemodtype-path:=module-type

模組型別替換的行為本質上與型別替換類似。它們適用於將簽章中的抽象模組型別細化為具體的模組型別,

# module type ENDO = sig module type T module F: T -> T end module Endo(X: sig module type T end): ENDO with module type T = X.T = struct module type T = X.T module F(X:T) = X end;;
module type ENDO = sig module type T module F : T -> T end module Endo : functor (X : sig module type T end) -> sig module type T = X.T module F : T -> T end

也可以用等效的模組型別替換具體的模組型別。

module type A = sig type x module type R = sig type a = A of x type b end end module type S = sig type a = A of int type b end module type B = A with type x = int and module type R = S

然而,這種替換從來都不是必要的。

破壞性模組類型替換會從簽名中移除模組類型替換

# module type ENDO' = ENDO with module type T := ENDO;;
module type ENDO' = sig module F : ENDO -> ENDO end

限制

如果模組類型替換或本地模組類型替換的右側不是模組類型路徑(modtype-path),則只有當替換的左側永遠不用作原始模組類型中第一類模組的類型時,破壞性替換才有效。

module type T = sig module type S val x: (module S) end module type Error = T with module type S := sig end
Error: 此 with 限制 S := sig end 會使封裝的模組格式錯誤。(請參閱手冊 12.7.3 節)
module type T = sig module type S := sig end val x: (module S) end
Error: 模組類型 S 不是封裝模組的有效類型:它被定義為匿名模組類型的本地替換(臨時名稱)。(請參閱手冊 12.7.3 節)