Ocaml 为什么调用的嵌套函子需要函子结果绑定到新模块名?

Ocaml 为什么调用的嵌套函子需要函子结果绑定到新模块名?,ocaml,functor,Ocaml,Functor,我有: 此代码有效: module Functor(M : sig end) = struct module NestedFunctor(M : sig end) = struct end end 但这是无效的: module V = Functor(struct end) module W = V.NestedFunctor(struct end) 据我所知,函子是一组输入模和一组允许的输出模之间的关系。但这个例子混淆了我的理解。为什么调用的嵌套函子需要函子结果绑定到新模块名 我

我有:

此代码有效:

module Functor(M : sig end) = struct
  module NestedFunctor(M : sig end) = struct
  end 
end
但这是无效的:

module V = Functor(struct end)
module W = V.NestedFunctor(struct end)
据我所知,函子是一组输入模和一组允许的输出模之间的关系。但这个例子混淆了我的理解。为什么调用的嵌套函子需要函子结果绑定到新模块名

我的编译器版本=4.01.0



我是OCaml的新手。当我发现函子时,我想象着

module M = Functor(struct end).NestedFunctor(struct end)
                          (*  ^ Error: Syntax error *)
我认为这是一个很好的工具,用于人性化的架构符号。 然后我很失望。我理解:这是语法错误。 我认为这种限制夸大了语法,降低了它的直观性


我的“为什么?”主要问题是在语言概念的背景下。

据我所知,没有深刻的答案。报告的错误是语法错误。也就是说,OCaml的语法不支持这种表示法


总结它的一种方法是,在模块表达式的语法中,点总是作为“长模块标识符”的一部分出现,即在两个大写标识符之间。我刚才检查了这个,这就是我看到的。

虽然我不认为这个限制是严格必要的,但它可能是由OCaml的模块类型系统中的某些限制引起的。在不涉及太多技术细节的情况下,OCaml要求所有中间模块类型都可以表示为语法签名。但对于函子,这有时是不可能的。例如,考虑:

Engine.MakeRunnerFor(ObservationStation
                       .Observe(Matrix)
                       .With(Printer))
根据此定义,functor
functor(struct end).Nested
的类型不能用OCaml语法表示。应该是这样的

module Functor(X : sig end) = struct
  type t = T of int
  module Nested(Y : sig end) = struct let x = T 5 end 
end
但是,
Functor(struct end).t在OCaml中不是有效的类型表达式,原因相当技术性(简而言之,允许这样的类型将使决定哪些类型是相等的——在类型检查期间是必要的——更加复杂)

命名中间模块通常可以避免这种困境。给定

functor(Y : sig end) -> sig val x : Functor(struct end).t end  (* not legal OCaml! *)
函子
A.Nested
具有类型

module A = Functor(struct end)

通过使用中的术语引用命名的中间结果
A
,类型等(模块类型、类类型等)可以通过扩展模块路径限定,其中限定符可以是函子调用,而非类型(核心表达式、模块表达式、类等)只能由模块路径限定,其中限定符必须是普通模块名称。
例如,您可以编写类型
函子(结构端).NestedFunctor(结构端).t
,但不能编写表达式
函子(结构端).NestedFunctor(结构端).x
或模块表达式
函子(结构端).NestedFunctor(结构端)

语法方面,在表达式中允许扩展模块路径是不明确的:表达式
F(M).x
被解析为应用于表达式
(M).x的构造函数
F
,其中
M
是一个构造函数,
操作符是记录字段访问操作符。这不会进行类型检查,因为
M
显然是
操作符无法应用的变体,但是在解析器中消除这一点会很复杂。可能还有其他我现在没有想到的含糊不清之处(对于一流的模块?)

就类型检查器而言,functor在类型指定中调用不是问题,它们是允许的。但是,参数本身必须是一个路径:您可以编写
Set.Make(String).t
,但不能编写
Set.Make(struct type t=String let compare=…end).t
。由于OCaml管理抽象类型的方式,在类型表达式中允许结构和一级模块将使类型检查器更加复杂。每次编写
Set.Make(String).t时,它都指定相同的抽象类型;但是如果你写

functor(Y : sig end) -> sig val x : A.t end
然后,
M1
M2
是不同的抽象类型。表述这一点的技术方法是,在OCaml中,函子应用程序是可应用的:将同一函子应用于同一参数总是返回相同的抽象类型。但是结构是生成性的:两次编写
struct…end
会产生不同的抽象类型-so
Set.Make(struct type t let compare=String.compare end)。t
Set.Make(struct type t let compare=String.compare end).t
-如果不小心在类型表达式中允许什么,生成类型会导致类型表达式之间出现非自反相等


代码生成不会受到太大影响,因为它可以将函子(struct…end).field作为让模块TMP=struct…end在函子(TMP)中。field

我是OCaml的新手。当我发现functor时,我想象了一些东西,比如
引擎.MakeRunnerFor(ObservationStation.Observe(Matix).With(Printer))
,我想:这很酷,也许人们会在以后的版本中添加对您建议的语法的支持。同时,你可以做你想做的事,只需要一点额外的编码。OCaml模块系统很酷,它是非帕雷尔的;看看我的答案。我不认为这是原因。任何函子应用程序表达式都有路径,因此未命名函子参数的问题不适用。我怀疑原因(如果有的话)可能更多地与明确的副作用有关:人们并不总是意识到函子有运行时成本,而这一限制鼓励人们不要继续重复应用同一个函子。@LeoWhite,我不明白你所说的“任何函子应用程序表达式都有路径”是什么意思
F(struct end)
是一个不是路径的函子应用程序示例。很抱歉,我对不支持的内容感到困惑。我原以为问题在于使用函子应用程序作为函子的参数。实际上,这是受支持的,并且它只是从一个被阻止的函子应用程序中投影一个嵌套模块。鉴于此
module M1 = Set.Make(struct type t let compare = String.compare end)
module M2 = Set.Make(struct type t let compare = String.compare end)