在 OCaml 中,给模块起别名到底做了什么?
In OCaml, what does aliasing a module do exactly?
在 OCaml 中,要将另一个模块引入作用域,您可以使用 open
。但是像这样的代码呢:
module A = struct
include B.C
module D = B.E
end
这是否会创建一个与 B
创建的模块无关的名为 A
的全新模块?或者 B
中的类型是否等同于这个新结构,例如 A.t
中的类型是否可以与 B.C.t
中的类型互换使用?
特别是,与 Rust 相比,我认为这与编写类似
的东西有很大不同
pub mod a {
pub use b::c::*;
pub use b::e as d;
}
是的,module A = struct include B.C end
创建了一个全新的模块并导出了 B.C
中的所有定义。从 B.C
导入的所有抽象类型和数据类型都与该模块明确相关。
换句话说,假设你有
module Inner = struct
type imp = Foo
type t = int
end
所以当我们导入 Inner
时,我们可以访问 Inner
定义,
module A = struct
include Inner
let x : imp = Foo
let 1 : t = 1
end
和A
中的Foo
构造函数与Inner
模块中的Foo
构造函数属于同一类型,因此下面的类型检查,
A.x = Inner.Foo
换句话说,include
不是单纯的copy-paste,而是这样的,
module A = struct
(* include Inner expands to *)
type imp = Inner.imp = Foo
type t = Inner.t = int
end
这种保留类型相等性的操作正式称为 strengthening 并且 always 在 OCaml 推断模块类型时应用。换句话说,类型系统永远不会忘记类型共享约束,删除它们的唯一方法是显式指定不公开共享约束的模块类型(或使用 module type of
构造,见下文)。
例如,如果我们将定义一个模块类型
module type S = sig
type imp = Foo
type t = int
end
然后
module A = struct
include (Inner : S)
end
会生成一个新的类型foo
,所以A.Foo = Inner.Foo
将不再进行类型检查。使用显式禁用模块类型强化的 module type of
构造也可以实现相同的效果,
module A = struct
include (Inner : module type of Inner)
end
将再次生成不同于 Inner.Foo
的 A.Foo
。请注意 type t
在所有实现中仍然兼容,因为它是 清单类型 并且 A.t
等于 Inner.t
不是通过共享约束而是因为两者都等于 int
.
现在,你可能会有疑问,
module A = Inner
和
module A = struct include Inner end
答案很简单。在语义上它们是等价的。而且,前面的并不是你想象的模块别名。两者都是 模块定义 。两者都将定义一个具有完全相同模块类型的新模块A
。
模块别名是存在于(模块)类型级别的特征,即在签名中,例如,
module Main : sig
module A = Inner (* now this is the module alias *)
end = struct
module A = Inner
end
所以模块别名的意思是,在模块级别,A
不仅与 Inner
具有相同的类型,而且它正是 Inner
模块。这为编译器和工具链打开了一些机会。例如,编译器可能会避开模块复制并启用模块打包。
但这一切都与观察到的语义无关,尤其是与打字无关。如果我们忘记显式相等(再次主要用于更优化的模块打包,例如,在沙丘中),那么模块的以下定义 A
module Main = struct
module A = Inner
end
与上面使用模块别名的完全相同。使用先前定义键入的任何内容都将使用新定义键入(模模块类型别名)。它一样强大。和下面一样强,
module Main = struct
module A = struct include Inner end
end
甚至以下,
module Main : sig
module A : sig
type imp = Impl.imp = Foo
type t = Impl.t = int
end
end = struct
module A = Impl
end
在 OCaml 中,要将另一个模块引入作用域,您可以使用 open
。但是像这样的代码呢:
module A = struct
include B.C
module D = B.E
end
这是否会创建一个与 B
创建的模块无关的名为 A
的全新模块?或者 B
中的类型是否等同于这个新结构,例如 A.t
中的类型是否可以与 B.C.t
中的类型互换使用?
特别是,与 Rust 相比,我认为这与编写类似
的东西有很大不同pub mod a {
pub use b::c::*;
pub use b::e as d;
}
是的,module A = struct include B.C end
创建了一个全新的模块并导出了 B.C
中的所有定义。从 B.C
导入的所有抽象类型和数据类型都与该模块明确相关。
换句话说,假设你有
module Inner = struct
type imp = Foo
type t = int
end
所以当我们导入 Inner
时,我们可以访问 Inner
定义,
module A = struct
include Inner
let x : imp = Foo
let 1 : t = 1
end
和A
中的Foo
构造函数与Inner
模块中的Foo
构造函数属于同一类型,因此下面的类型检查,
A.x = Inner.Foo
换句话说,include
不是单纯的copy-paste,而是这样的,
module A = struct
(* include Inner expands to *)
type imp = Inner.imp = Foo
type t = Inner.t = int
end
这种保留类型相等性的操作正式称为 strengthening 并且 always 在 OCaml 推断模块类型时应用。换句话说,类型系统永远不会忘记类型共享约束,删除它们的唯一方法是显式指定不公开共享约束的模块类型(或使用 module type of
构造,见下文)。
例如,如果我们将定义一个模块类型
module type S = sig
type imp = Foo
type t = int
end
然后
module A = struct
include (Inner : S)
end
会生成一个新的类型foo
,所以A.Foo = Inner.Foo
将不再进行类型检查。使用显式禁用模块类型强化的 module type of
构造也可以实现相同的效果,
module A = struct
include (Inner : module type of Inner)
end
将再次生成不同于 Inner.Foo
的 A.Foo
。请注意 type t
在所有实现中仍然兼容,因为它是 清单类型 并且 A.t
等于 Inner.t
不是通过共享约束而是因为两者都等于 int
.
现在,你可能会有疑问,
module A = Inner
和
module A = struct include Inner end
答案很简单。在语义上它们是等价的。而且,前面的并不是你想象的模块别名。两者都是 模块定义 。两者都将定义一个具有完全相同模块类型的新模块A
。
模块别名是存在于(模块)类型级别的特征,即在签名中,例如,
module Main : sig
module A = Inner (* now this is the module alias *)
end = struct
module A = Inner
end
所以模块别名的意思是,在模块级别,A
不仅与 Inner
具有相同的类型,而且它正是 Inner
模块。这为编译器和工具链打开了一些机会。例如,编译器可能会避开模块复制并启用模块打包。
但这一切都与观察到的语义无关,尤其是与打字无关。如果我们忘记显式相等(再次主要用于更优化的模块打包,例如,在沙丘中),那么模块的以下定义 A
module Main = struct
module A = Inner
end
与上面使用模块别名的完全相同。使用先前定义键入的任何内容都将使用新定义键入(模模块类型别名)。它一样强大。和下面一样强,
module Main = struct
module A = struct include Inner end
end
甚至以下,
module Main : sig
module A : sig
type imp = Impl.imp = Foo
type t = Impl.t = int
end
end = struct
module A = Impl
end