Vic*_*let 5 ocaml types functional-programming
现在,我有一个用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
Run Code Online (Sandbox Code Playgroud)
例如,呈现博客帖子将基于此:
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)
Run Code Online (Sandbox Code Playgroud)
仅具有t -> (string * string) list提取所有可能值的函数更复杂,但它在初始化期间确保提供所有必需的模板变量.
添加新字段(例如permalink)很简单,但需要手动编辑代码.我正试图摆脱这个过程,转向这样一种情况,即permalink整个应用程序中的任何内容都在模块中干净地分隔开,并且仅应用于应该使用的任何地方.
这导致我最初的装饰模式沿着以下方向:
module WithPermalink = functor(Def:TEMPLATE_DEF) -> struct
type t = < permalink : string ; inner : Def.t >
let source = Def.source
let mapping =
( "permalink", (fun x -> x # permalink) )
:: List.map (fun (k,f) -> (k, (fun x -> f (x#inner)))) Def.mapping
end
Run Code Online (Sandbox Code Playgroud)
然而,由于两个原因,这种方法仍然不能令人满意,我正在寻找一种更好的模式来解决它们.
第一个问题是这种方法仍然需要我更改模板定义代码(我仍然需要应用WithPermalink仿函数).我想要一个解决方案,将模块中的永久链接添加到模块Post中非侵入式执行Permalink(这可能涉及实现模板系统的某种通用可扩展性).
在第二个问题是,如果我需要申请几个这样的仿函数(有一个日期,标签,评论...),那么它们所使用其应用成为相关的数据类型,因此任何代码的顺序.这并不妨碍代码工作,但是在定义中,交换操作在其实现中是非交换的,这是令人沮丧的.
我怎样才能做到这一点?
编辑
在给予主题更多思考之后,我已经确定了可扩展对象设计.这就是我期望它会在一些预处理器美化后的预期:
(* 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) %}
Run Code Online (Sandbox Code Playgroud)
实现将是相当标准的:当post定义可扩展类型ExtenderImplementation_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
Run Code Online (Sandbox Code Playgroud)
然后,定义字段link : string = ""将转换为:
let link : (Post.post,string) lens = Post.ExtenderImplementation_post.extend ""
Run Code Online (Sandbox Code Playgroud)
getter,setter和初始化的转换相当简单,并使用字段实际上是镜头的事实.
您是否看到任何潜在的设计问题或可能的扩展方法?
您要避免的是在您定义的标签周围编写样板。也许您可以使用 camlp4 自动生成一组标签的模块代码?
您希望能够向对象类型添加方法。我认为目前不可能。
我知道的唯一可能的方法是使用类型感知的预处理器。在 Haskell 中,他们有 HaskellTemplate,这是一个预处理器,可以在输入过程中扩展宏,并了解输入环境。
两年前,我为 OCaml 编写了一个等效的原型,它运行良好,可以在此处访问ocaml-3.12.0,并提供一些基本示例。但要做你想做的事,你需要了解 OCaml AST 并能够从前一个 AST 生成新的 AST(目前没有报价可以轻松生成 AST)。