Types 扩展插件体系结构中的类型

Types 扩展插件体系结构中的类型,types,functional-programming,ocaml,Types,Functional Programming,Ocaml,现在,我有一个用OCaml编写的工作HTML模板系统。一般设计是,单个模板是由应用于以下模块类型的函子返回的模块: module type TEMPLATE_DEF = sig type t (* The type of the data rendered by the template. *) val source : string (* Where the HTML template itself resides. *) val mapping : (string * (t -&

现在,我有一个用OCaml编写的工作HTML模板系统。一般设计是,单个模板是由应用于以下模块类型的函子返回的模块:

module type TEMPLATE_DEF = sig
  type t (* The type of the data rendered by the template. *)
  val source : string (* Where the HTML template itself resides. *)
  val mapping : (string * (t -> string)) list 
end
例如,呈现博客文章将基于以下内容:

module Post = Loader.Html(struct
  type t = < body : string ; title : string >
  let source  = ...
  let mapping = [ "body", (fun x -> x#body) ; "title", (fun x -> x#title) ]
end)
然而,由于两个原因,这种方法仍然不令人满意,我正在寻找一种更好的模式来解决这两个问题

第一个问题是,这种方法仍然需要我更改模板定义代码(我仍然需要使用permalinkfunctor应用
)。我想要一个解决方案,其中将permalink添加到模板
Post
将由
permalink
模块以非侵入方式执行(这可能涉及实现模板系统的某种通用扩展性)

第二个问题是,如果我需要应用几个这样的functor(有日期、标记、注释……),那么应用它们的顺序将与数据类型相关,从而与使用它的任何代码相关。这并不会阻止代码工作,但按照定义,交换操作在其实现中是非交换的,这是令人沮丧的

我怎样才能做到这一点

编辑

在对这个主题进行了更多思考之后,我决定采用可扩展对象设计。这就是我所期望的经过一些预处理器美化后的效果:

(* Module Post *)
type post = {%
  title : string = "" ;
  body  : string = "" 
%}   

let mapping : (string * (post -> string)) list ref = 
  [ "title", (%title) ;
    "body",  (%body) ]

(* Module Permalink *)
type %extend Post.post = {% 
  link : string = "" 
%}

Post.mapping := ("permalink", (%link)) :: !Post.mapping

(* Defining the template *)
module BlogPost = Loader.Html(struct
  type t = Post.post
  let source = ...
  let mapping _ = !Post.mapping
end)

(* Creating and editing a post *)
let post = {% new Post.post with 
  Post.title     = get_title () ;
  Post.body      = get_body () ;
  Permalink.link = get_permalink () ; 
%}

let post' = {% post with title = BatString.strip (post % Post.title) %}
实现将相当标准:当定义了可扩展类型
post
时,使用此类代码在该位置创建一个
extender实现\u post
模块:

module ExtenderImplementation_post : sig
  type t 
  val field : 'a -> (t,'a) lens
  val create : unit -> t
end = struct
  type t = (unit -> unit) array
  let fields : t ref = ref [| |]
  let field default =
    let store = ref None in
    let ctor () = store := Some default in
    let n = Array.length !fields in
    fields := Array.init (n+1) (fun i -> if i = n then ctor else (!fields).(i)) ;
    { lens_get = (fun (t:t) -> t.(n) () ; match !store with
      | None   -> assert false
      | Some s -> store := None ; s) ;
      lens_set = (fun x (t:t) -> let t' = Array.copy t in
                            t'.(n) <- (fun () -> store := Some x) ; t') }
  let create () = !fields
end
type post = ExtenderImplementation_post.t
getter、setter和初始化的转换相当简单,并且使用了字段实际上是透镜这一事实


您是否看到这种方法存在任何潜在的设计问题或可能的扩展?

您希望避免的是围绕所定义的标签编写样板文件。也许您可以使用camlp4自动生成一组标签的模块代码

编辑 您希望能够向对象类型添加方法。我认为目前这是不可能的

我所知道的唯一可能的方法是使用类型识别的预处理器。在Haskell中,他们有Haskell模板,这是一个预处理器,可以在键入过程中扩展宏,并了解键入环境


两年前,我为OCaml编写了一个等效的原型,它工作得很好,在OCaml-3.12.0中可以访问,并提供了一些基本示例。但要想做您想做的事情,您需要了解OCaml AST,并能够从上一个AST生成新的AST(目前没有容易生成AST的引用)。

在我看来,这个用例更适合面向对象的解决方案。我愿意接受任何建议,包括面向对象的建议,只要(初始化后)运行时错误是不可能的。不,这不是我想要的,事实上我已经有了它,我只是在这里展示生成代码的简化版本。我正在寻找在不改变定义的情况下添加更多标签的方法,以便将这些附加标签封装在单独的模块中。我认为唯一的解决方案是使用ocaml-模板,但尚未发布:-(
module ExtenderImplementation_post : sig
  type t 
  val field : 'a -> (t,'a) lens
  val create : unit -> t
end = struct
  type t = (unit -> unit) array
  let fields : t ref = ref [| |]
  let field default =
    let store = ref None in
    let ctor () = store := Some default in
    let n = Array.length !fields in
    fields := Array.init (n+1) (fun i -> if i = n then ctor else (!fields).(i)) ;
    { lens_get = (fun (t:t) -> t.(n) () ; match !store with
      | None   -> assert false
      | Some s -> store := None ; s) ;
      lens_set = (fun x (t:t) -> let t' = Array.copy t in
                            t'.(n) <- (fun () -> store := Some x) ; t') }
  let create () = !fields
end
type post = ExtenderImplementation_post.t
let link : (Post.post,string) lens = Post.ExtenderImplementation_post.extend ""