类型安全的模板变量替换

时间:2015-08-16 17:13:56

标签: ocaml template-engine

我有一种类型安全模板语言的想法,它将使用多态变体作为可替代文本的类型安全变量的来源,例如:

type 'a t = Var of 'a | Text of string | Join of 'a t * 'a t  

let rec render ~vars = function
  | Text source -> source
  | Var label -> vars label
  | Join (left, right) -> render left ~vars ^ render right ~vars

let result = render (Join (Var `Foo, Text "bar")) ~vars:(function `Foo -> "foo");;

let () = assert (result = "foobar")  

这很好:编译器会强制你不要忘记替换变量,或者你不会在变量名中输入错误 - 多亏了多态变体。

但是,我发现了两个问题:

  1. 您可能会意外地提供未使用的变量。

  2. 如果模板不包含变量,您仍然需要提供~vars函数,唯一可行的函数是fun _ -> ""fun _ -> assert false,这会影响类型 - 如果模板发生变化,则保证安全。

  3. 我正在寻找有关上述问题的建议,但我也非常感谢有关API设计的任何适用建议。

2 个答案:

答案 0 :(得分:3)

没有什么能迫使你总是使用多态变体。您可以使用void类型保证与每个多态变体不同。

type void
let empty_vars : void -> string = fun _ assert false

当您将其应用于空模板时,最终会使用

let result = render (Text "bar") ~vars:empty_vars

这样,如果您稍后向模板添加变量,您将立即通过类型错误注意到它。

对于未使用的变量,我建议的最好也不是使用多态变体:

type v = Foo
let result = render (Join (Var Foo, Text "bar")) ~vars:(function Foo -> "foo");;

这只会捕获函数定义中未使用的案例,但当然如果删除模板的一部分,您将不会注意到任何内容。

另一种具有相似属性但可能或可能不符合您口味的解决方案是使用对象。

let rec render ~vars = function
  | Text source -> source
  | Var label -> label vars
  | Join (left, right) -> render left ~vars ^ render right ~vars


let foo v = v#foo
let result = render (Join (Var foo, Text "bar")) ~vars:object method foo = "foo" end

这样,当没有使用变量时,你可以保持相同的模式:

let result = render (Text "bar") ~vars:object end

但仍然没有未使用的变量检查。

答案 1 :(得分:1)

我认为多态变体是不可能的。 render函数的类型是:

val render:var :('a - > string) - > 't - >串

,部分应用程序render (Join (Var `Foo, Text "var"))具有以下类型:

vars:([> `Foo ] -> string) -> string

您要做的是关闭已打开的变体类型[> `Foo ]并将其限制为[ `Foo ] -> string,以便排除可以获得更大输入的函数,例如[< `Foo | `Bar ] -> string

限制类型的唯一方法是添加类型约束:(vars : [ `Foo ] -> string),列出您想要显式的所有标记,但这是您要避免的...