我正在开发OCaml中的应用程序,并预计代码库最终会生成多个可执行文件。每个应用程序的配置选项也会发生变化。
我对OCaml的模块系统比较陌生,但我尝试使用它们来强制执行我想要的结构。如果有一种完全不同的方法符合这些标准,请分享并解释它如何比其他方法更好(最后请参见粗略的想法)。
t
类型(以及接受t
的所有函数)的签名都不得更改,即使其配置规范发生更改也是如此。但是,可以访问t
的应用程序功能可以自动访问配置规范提供的所有内容。使用
测试的所有内容$ ocaml --version
The OCaml toplevel, version 4.03.0
$ ocamlbuild --version
ocamlbuild 0.9.2
$ ocamlbuild sandbox.byte
APPLICATION
仅使用某种内部CONFIGURABLE_SYSTEM
类型构建config
:
module type CONFIGURABLE_SYSTEM = sig
type t
type config
val make : config -> t
end
module type APPLICATION = sig
include CONFIGURABLE_SYSTEM
val launch : t -> unit
end
这是一个满足上述签名的演示应用程序:
module DemoApp : APPLICATION = struct
type config = { color : color_scheme }
and color_scheme = | HighContrast | Chromatic
type t = (config * string)
let make cfg = (cfg, "default string state")
let launch (cfg, _str) =
match cfg.color with
| HighContrast -> print_string "Using high contrast settings...\n"
| Chromatic -> print_string "Using chromatic settings...\n"
let default_config = { color = HighContrast }
end
make
的 目标:launch
和DemoApp
let () = ...
。
问题:从config
模块外部构建DemoApp
记录。 (请注意let default_config
已成功构建内的 DemoApp
中的一个。)
let () =
let config = (*** What goes here? ***)
let demo = DemoApp.make config in
DemoApp.launch demo
以最小的仪式尝试明显的方法:
let () =
let config = { color = HighContrast } in
()
Error: unbound record field color
失败。并不完全令人惊讶,因为color
字段位于不同的模块中。
创建一个ad-hoc模块,在其中打开DemoApp
,在那里创建一个config
实例,然后从外面抓取它:
let () =
let config =
let module M = struct
open DemoApp
let config = { color = HighContrast }
end
in
M.config
in
()
令人惊讶的是,Error: unbound record field color
也失败了。
咬紧牙关,定义一个只能用作DemoApp
配置类型的顶级类型。
type demo_config =
{
color : color_scheme
}
and color_scheme = | HighContrast | Chromatic
重新定义DemoApp
的{{1}}类型以使用它:
config
......试一试:
Module DemoApp : APPLICATION = struct
type config = demo_config
... (* everything else is the same as above *)
end
错误:此表达式的类型为demo_config 但是期望表达式为DemoApp.config
哎哟。即使前者被定义为后者,模块系统也不会将let () =
let cfg = { color = HighContrast } in
let demo = DemoApp.make cfg in
()
标识为DemoApp.config
。这是因为demo_config
和CONFIGURABLE_SYSTEM.config
是抽象的,而APPLICATION.config
会产生DemoApp
,这意味着它的APPLICATION
定义是隐藏的吗?如果是这样,有没有办法强制config
对APPLICATION
,强加的所有约束,除了隐藏具体的DemoApp
类型?
...这里有一些其他我尚未尝试过的策略,但可能会有效:
config
结构,如APPLICATION
,都由模块参数化,理想情况下是签名,代表配置规范。调用者定义他们自己的模块结构,满足应用程序的配置规范,使用它来创建自己的专用应用程序结构。然后,他们创建一个配置实例,并将其传递给专门的应用程序结构的构造函数,以生成他们配置的应用程序。DemoApp
接受满足配置签名的任何模块。此签名必须在make
中是抽象的,并且由每个CONFIGURABLE_SYSTEM
结构具体定义。应用程序构造函数的调用者还必须有一种方法来构造满足配置签名的模块(因此,定义具体的配置设置)。APPLICATION
类型可以定义为行多态对象签名,config
调用者可以传递任何至少具有该签名中定义的字段的对象。我不太兴奋这种方法,因为它可以鼓励所有应用程序的通用配置,并导致各种应用程序配置中奇怪的命名/解释冲突(例如,app1的配置要求make
字段为{{1}和app2的配置需要x
,或者更糟糕的是他们期望相同的名称/类型,但不同地解释它们。答案 0 :(得分:2)
您的问题在这里:
module DemoApp : APPLICATION = struct
通过此模块类型注释,您可以约束DemoApp
的类型。这意味着t
和config
是抽象的(colorscheme
是隐藏的),因为它是APPLICATION
中声明它们的方式。
您希望确保DemoApp
尊重APPLICATION
签名,同时仍然暴露实际的数据类型。您只需删除注释,它就可以正常工作。
在.mli
中,你会有类似的东西:
module DemoApp : sig
type config = { color : color_scheme }
and color_scheme = | HighContrast | Chromatic
type t = (config * string)
(* include APPLICATION while style exposing the concrete types. *)
include APPLICATION with type t := t and type config := config
end
请注意,您所希望的名称是"透明归属"。正如您所发现的,通常的模块类型归属隐藏了模块中类型的实现(它"不透明")。透明的归属不会。