【问题标题】:Function to create one of two record types dynamically动态创建两种记录类型之一的函数
【发布时间】:2012-12-10 23:31:42
【问题描述】:

我尝试在 Ocaml 中实现这种 OO 情况: 两个类X1X2,都是子类型XX1 <: XX2 <: X),我想编写一个动态返回X 的函数,它可以是X1 或@987654328 @。

但是我听说避免使用 Ocaml 中的类并改用模块通常会很好,所以我试图像这样表示我的问题(过于简化但仍然很重要): 两个模块X1X2,我希望我的函数能够动态决定返回X1.t 还是X2.t

module type X = sig
  type choice
  type t
  (* some methods we don't care about in this instance, like
     val modifySomething : t -> t *)
end

module Xbase = struct
  type choice = Smth | SmthElse
end

module X1 = (
struct
  include Xbase
  type t = { foo : int; bar : int }
end : X)

module X2 = (
struct
  include Xbase
  type t = { foo : int; star : int }
end : X)

module XStatic =
struct
  (* construct either an X1.t or X2.t from the string *)
  let read : string -> 'a =
    function
    | "X1" -> { X1.foo = 0, bar = 0 }
    | "X2" -> { X2.foo = 1, star = 1 }
end

但这在read 函数中使用Error: Unbound record field label X1.foo 失败。 我尝试了不同的排列方式,例如使用let open X1 in { foo = 0, ... },但无济于事。

我解决这个问题的方法是根本错误的(即我应该使用类,因为这对模块来说是不可能/不切实际的)还是我只是错过了一些微不足道的东西?

编辑:澄清了我要解决的问题并将module X 重命名为module XBase 以区别于module type X

【问题讨论】:

    标签: ocaml


    【解决方案1】:

    最简单的方法是使用 sum 类型(免责声明:我没有尝试编译代码):

    module X1 = struct
      type t = { foo : int; bar : string }
      let modify_foo = ...
    end
    module X2 = struct
      type t = { foo : int; star : bool }
      let modify_foo = ...
    end
    type x1_or_x2 =
      | Left of X1.t
      | Right of X2.t
    
    let read = function
      | "X1" -> Left { X1.foo = 1; bar = "bar" }
      | "X2" -> Right { X2.foo = 1; star = true }
    
    let modify_foo = function
      | Left x1 -> Left (X1.modify_foo x1)
      | Right x2 -> Right (X2.modify_foo x2)
    

    如果您想利用X1.tX2.t 共享一些共同结构这一事实,您可以分解类型。这个想法是它们与产品类型同构,分别为common_part * specific_to_x1common_part * specific_to_x2。因此x1_or_x2 类型为(common * specific_to_x1) + (common * specific_to_x2),它等价于common * (specific_to_x1 + specific_to_x2)

    type common = { foo : int }
    let modify_foo_common : common -> common = ...
    
    type specific_x1 = { bar : string }
    type specific_x2 = { star : bool }
    
    type x1_or_x2 = common * specific_x1_or_x2
    and specific_x1_or_x2 =
      | Left of X1.t
      | Right of X2.t
    
    let read = function
      | "X1" -> { foo = 1 }, Left { bar = "bar" }
      | "X2" -> { foo = 1 }, Right { star = true }
    
    let modify_foo (common, specific) = (modify_foo_common common, specific)
    

    这样,作用于公共部分的定义不会重复,而是可以声明一次。

    PS:另请参阅这个非常相关的问题,您可能会对此感兴趣并且有一个很好的答案(镜头!):Ptival: Statically “extend” a record-ish data type without indirection hassle

    【讨论】:

      【解决方案2】:

      错误Unbound record field label X1.foo是由于X1X2具有模块签名Xbase,它只有choice类型(而不是t类型)。 IE。语法: X 隐藏了所有不属于Xbase 签名的X1 的值和类型。

      但是即使你纠正了这个错误,也会出现更重要的事情:函数read的返回类型是什么?不能同时是X1.tX2.t

      【讨论】:

      • 相反,module type X 确实有 type t(就在开头)——抱歉,我将初始模块及其类型都命名为相同的名称,这让我感到困惑。
      • 关于第二段:我试图模仿类似于类子类型的东西。 X1 <: XX2 <: X,我想动态创建 X1 或 X2 并将其作为 X 返回。我将更新问题以使其更清晰。
      【解决方案3】:

      一旦您将模块X1X2 强制转换为模块类型X,您就失去了在这些模块之外如何构造内部类型的信息。其余代码(即除模块内容之外的所有内容)将无法知道 X1.tX2.t 是由什么组成的:这些类型对于其他所有内容都抽象,但各自的模块。

      您的问题的 OOP 方法可能是创建默认构造函数,并在需要时调用它们。在您的情况下,这意味着在模块中定义默认值,在接口(模块类型)中添加声明,以便外部代码仍然可以创建这些类型的值。

      module type X = sig
          type t
          val default : unit -> t
          (* etc. *)
      end;;
      
      module X1 : X = struct
          include XBase     
          type t = {foo : int; bar : int}
          (* here I can change fields adlib *)
          let default () = {foo = 0; bar = 1}
         (* ... *)
      end;;
      (* here I don't have access to X1.t fields anymore *)
      

      请注意,如果您的类型不包含可变字段或引用,您可以将default 设为t 的简单值,而不是函数,但也许您希望保留在其他模块中拥有此类字段的可能性.

      这里的要点是,您需要保持您的类型公开,或者为您的抽象类型提供构造和操作值的方法(我想后者已经在您的代码中在某种程度上实现了)。

      如果您不更改或无法更改模块类型 X,那么您也许可以在上层强制签名,使用 mli 文件对包含 X1 和 @ 的模块以外的其他模块进行强制转换987654331@(也就是说,如果您的代码遵循这样的设计)。

      另一种方法是在进行强制转换时添加有关t 的类型信息。例如,下面是 X1 类型的声明,其中 choice 类型显式(假设它已经存在于 X 中):

      module X1 : X with type choice = XBase.choice =
      struct
         (* body of the X1 module *)
      end;;
      (* now I can access X1.choice constants from here, if XBase.choice is visible too *)
      

      您可以对其类型t 执行类似操作,然后从外部代码访问其内容。从设计的角度来看,如果您已经在模块中提供了操作符来操作 t 类型的值,那么这显然不是处理问题的最佳方式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-17
        相关资源
        最近更新 更多