第 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 中引入)

规范::= ...
 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: 未绑定类型构造函数 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 节)