Ocaml 类型安全模板变量替换
我有一种类型安全模板语言的想法,它将多态变量用作可以替代文本的类型安全变量源,例如:Ocaml 类型安全模板变量替换,ocaml,template-engine,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 ^ re
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")
这一切都很好:编译器将强制您不要忘记替换变量,或者由于多态变量,您不会在变量名中出现键入错误
然而,我发现两个问题:
~vars
函数,唯一有效的函数是fun->“
或fun->assert false
,这将在模板发生更改时压缩类型安全性我正在寻找关于上述问题的建议,但我也很感激任何关于API设计的适用建议。我认为多态变体是不可能的。
render
函数的类型为:
val render:var:('a->string)->'a t->string
部分应用程序呈现(Join(Var`Foo,Text“Var”)
具有以下类型:
vars:([> `Foo ] -> string) -> string
您要做的是关闭打开的变量类型[>`Foo]
,并将其限制为[`Foo]->string
,以排除可以获得更大输入的函数,如[<`Foo}`Bar]->string
限制类型的唯一方法是添加一个类型约束:
(vars:[`Foo]->string)
,明确列出您想要的所有标记,但这是您想要避免的…我认为使用多态变体是不可能的。render
函数的类型为:
val render:var:('a->string)->'a t->string
部分应用程序呈现(Join(Var`Foo,Text“Var”)
具有以下类型:
vars:([> `Foo ] -> string) -> string
您要做的是关闭打开的变量类型[>`Foo]
,并将其限制为[`Foo]->string
,以排除可以获得更大输入的函数,如[<`Foo}`Bar]->string
限制类型的唯一方法是添加类型约束:
(vars:[`Foo]->string)
,明确列出您想要的所有标记,但这是您想要避免的…没有任何东西强迫您始终使用多态变体。你可以有一个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
但是仍然没有未使用的变量检查。没有任何东西强迫您始终使用多态变量。你可以有一个
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
但是仍然没有未使用的变量检查。关于'a*'a->string的
变量呢?这看起来很难看,但必须将打印机与变量关联起来。我的解决方案是在类型构造函数中将它们关联起来。@nnarklrh不确定我是否遵循。这是什么'a->string
打印机?你能举个例子吗?对不起,我错了。它应该是'a*字符串的Var
。然后,写“Var(`Foo,Foo)”。但是它看起来太麻烦了。那么'a*'a->string的Var呢?这看起来很难看,但必须将打印机与变量关联起来。我的解决方案是在类型构造函数中将它们关联起来。@nnarklrh不确定我是否遵循。这是什么'a->string
打印机?你能举个例子吗?对不起,我错了。它应该是'a*字符串的Var
。然后,写“Var(`Foo,Foo)”。但是它看起来太麻烦了。你的空变量解决方案很有趣。但是,您需要记住将函数作为~vars
提供。我本想将~vars
设为默认值空变量
,但随后它推断渲染
为~vars:(void->string)->void t->string
,这没有用。有什么建议吗?希望默认参数具有可重写的类型是一个经典问题,遗憾的是目前没有通用的好解决方案(一些人对如何在编译器中实现这一点有想法)。但是在这种情况下,您有一个稍微轻量级的解决方案:声明函数的两个版本:render:vars:('a->string)->'a t->string
和render:void t->string
,render'
是预应用于empty\u vars
的解决方案,这很有趣。但是,您需要记住将函数作为~vars
提供。我本想将~vars
设为默认值空变量
,但随后它推断渲染
为~vars:(void->string)->void t->string
,这没有用。有什么建议吗?这是一个典型的问题,希望默认参数的类型可以被覆盖