OCaml:使用模块签名,类型或其他内容进行解耦

时间:2016-08-28 20:35:08

标签: module architecture ocaml

我正在开发OCaml中的应用程序,并预计代码库最终会生成多个可执行文件。每个应用程序的配置选项也会发生变化。

我对OCaml的模块系统比较陌生,但我尝试使用它们来强制执行我想要的结构。如果有一种完全不同的方法符合这些标准,请分享并解释它如何比其他方法更好(最后请参见粗略的想法)。

  1. 每个应用程序都必须指定自己的配置签名/类型。
  2. 每个应用程序只能使用指定配置的实例化构建。
  3. 只要结果满足应用程序的规范,配置构造就会在呼叫站点处理,但调用者会选择。
  4. 每个应用程序的t类型(以及接受t的所有函数)的签名都不得更改,即使其配置规范发生更改也是如此。但是,可以访问t的应用程序功能可以自动访问配置规范提供的所有内容。
  5. 使用

    测试的所有内容
    $ 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

    目标launchDemoApp let () = ...

    问题:从config模块外部构建DemoApp记录。 (请注意let default_config已成功构建内的 DemoApp中的一个。)

    let () =
      let config = (*** What goes here? ***)
      let demo = DemoApp.make config in
      DemoApp.launch demo
    

    尝试1

    以最小的仪式尝试明显的方法:

    let () =
      let config = { color = HighContrast } in
      ()
    

    Error: unbound record field color失败。并不完全令人惊讶,因为color字段位于不同的模块中。

    尝试2

    创建一个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也失败了。

    尝试3

    咬紧牙关,定义一个只能用作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_configCONFIGURABLE_SYSTEM.config是抽象的,而APPLICATION.config会产生DemoApp,这意味着它的APPLICATION定义是隐藏的吗?如果是这样,有没有办法强制configAPPLICATION强加的所有约束,除了隐藏具体的DemoApp类型?

    如果这是一个死胡同

    ...这里有一些其他我尚未尝试过的策略,但可能会有效:

    1. Functors :每个config结构,如APPLICATION,都由模块参数化,理想情况下是签名,代表配置规范。调用者定义他们自己的模块结构,满足应用程序的配置规范,使用它来创建自己的专用应用程序结构。然后,他们创建一个配置实例,并将其传递给专门的应用程序结构的构造函数,以生成他们配置的应用程序。
    2. 一流模块DemoApp接受满足配置签名的任何模块。此签名必须在make中是抽象的,并且由每个CONFIGURABLE_SYSTEM结构具体定义。应用程序构造函数的调用者还必须有一种方法来构造满足配置签名的模块(因此,定义具体的配置设置)。
    3. 行多态:我已经读过一些关于对象系统如何支持行多态的内容。也许每个应用程序的APPLICATION类型可以定义为行多态对象签名,config调用者可以传递任何至少具有该签名中定义的字段的对象。我不太兴奋这种方法,因为它可以鼓励所有应用程序的通用配置,并导致各种应用程序配置中奇怪的命名/解释冲突(例如,app1的配置要求make字段为{{1}和app2的配置需要x,或者更糟糕的是他们期望相同的名称/类型,但不同地解释它们。

1 个答案:

答案 0 :(得分:2)

您的问题在这里:

module DemoApp : APPLICATION = struct

通过此模块类型注释,您可以约束DemoApp的类型。这意味着tconfig是抽象的(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

请注意,您所希望的名称是"透明归属"。正如您所发现的,通常的模块类型归属隐藏了模块中类型的实现(它"不透明")。透明的归属不会。