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
    ,这没有用。有什么建议吗?这是一个典型的问题,希望默认参数的类型可以被覆盖