使用具有高阶函数的GADT

格伦斯

我正在尝试为“异构树”建模。一棵树,其中的节点具有不同的“种类”,每个“种类”都受它们可能包含的子代“种类”的限制:

type id = string
type block
type inline

type _ node =
  | Paragraph : id * inline node list -> block node
  | Strong : id * inline node list -> inline node
  | Text : id * string -> inline node

然后可以这样定义一棵树:

let document =
    Paragraph ("p1", [
      Text ("text1", "Hello ");
      Strong ("strong1", [
        Text ("text2", "Glorious")
      ]);
      Text ("text3", " World!")
  ])

通常,这将对每个“种类”的节点使用单独的变体来完成,但是我试图将其定义为GADT,以便能够使用在每种类型的节点上进行模式匹配的高阶函数来操纵树:

function
  | Text ("text2", _) ->
    Some (Text ("text2", "Dreadful"))
  | _ ->
    None

我遇到的问题是定义接受上述高阶函数并将其应用于每个节点的函数。到目前为止,我有这个:

let rec replaceNode(类型a)(f:节点->节点选项)(节点:节点):节点=
  将f节点与
  |匹配一些otherNode-> otherNode 
  | 无->
    |匹配节点 段落(id,子项)->段落(id,(List.map(replaceNode f)子项)) 
    | Strong(id,children)-> 
      Strong(id,(List.map(replaceNode f)子级))
    | 文字(_,_)->节点
      

但是编译器在突出显示的行上给了我以下错误

该表达式的类型为block node->一个节点选项,但是期望使用一个表达式,类型为block node->一个node选项。block的这个实例是模棱两可的:它将逃避其方程式的范围

或者,如果我改变的类型f'a node -> 'a node option我得到这个错误,而不是

该表达式的类型为节点,但是期望为节点类型的表达式。类型构造函数a会逃避其作用域

显然,我并没有完全理解局部抽象类型(或者就此而言确实是GADT),但是从我的理解来看,似乎出现了这些错误,因为顾名思义,该类型是“局部”的,而在外面,传递下去会“泄漏”它,我猜呢?

所以我的问题是,首要的是:这是否有可能做到(通过“ this”,我想我想在高阶函数中对GADT进行模式匹配,但我什至不知道这是实际的问题) ?

在这里有所有代码的游乐场

八时

这里有两个根本问题(由于存在GADT而有些混乱)。第一个问题是replaceNode第二级多态函数。确实,在第一个匹配中,f将应用于type的节点a node,但是在Paragraph分支内部,将其应用于type的节点inline node此处的类型检查器错误因List.map调用而有些复杂,但将函数重写为

let rec replaceNode (type a) (f:a node -> a node option) 
(node:a node): a node =
  match f node with
  | Some otherNode -> otherNode
  | None ->
    match node with
    | Paragraph(id, []) -> Paragraph(id,[])
    | Paragraph (id, a :: children) -> 
      Paragraph (id, f a :: (List.map (replaceNode f) children))
    | Strong (id, children) ->
      Strong (id, (List.map (replaceNode f) children))
    | Text (_, _) -> node;;

产生一个更直接的错误:

错误:此表达式具有类型内联节点,
但是期望一个类型为节点的表达式
类型内联与类型a不兼容

因此,问题在于我们需要确保f适用于任何类型的类型检查器,a而不仅仅是原始类型a换句话说,的类型f应为'a. 'a node -> 'a node option(aka forall 'a. 'a -> 'a node option)。不幸的是,仅在OCaml中的第一个位置(prenex)才可能使用显式多态注释,因此我们不能仅更改fin的类型replaceNode但是,可以通过使用多态记录字段或方法来解决此问题。

例如,使用记录路径,我们可以定义一个记录类型mapper

type mapper = { f:'a. 'a node -> 'a node option } [@@unboxed]

其中该字段f具有正确的显式多态符号(又称通用量化),然后将其用于replaceNode

let rec replaceNode (type a) {f} (node: a node): a node =
  match f node with
  | Some otherNode -> otherNode
  | None ->
    match node with
    | Paragraph (id, children) -> 
      Paragraph (id, (List.map (replaceNode {f}) children))
    | Strong (id, children) ->
      Strong (id, (List.map (replaceNode {f}) children))
    | Text (_, _) -> node

但是随后弹出第二个问题:该replaceNode函数具有type mapper -> inline node -> inline node内联类型来自哪里?这次的问题是多形性递归。如果没有显式的多态注释,则其类型replaceNode在其递归定义被视为常量。换言之,类型检查器认为replaceNode具有用于类型mapper -> 'elt node -> 'elt nodeGIVEN 'eltparagraphstrong分支中,该children列表是的列表inline node因此List.map (replaceNode {f}) children意味着用于类型检查器'elt=inline因而类型replaceNodemapper -> inline node -> inline node

要解决此问题,我们需要添加另一个多态注释。幸运的是,这次,我们可以直接添加它:

let rec replaceNode: type a. mapper -> a node -> a node =
  fun {f} node -> match f node with
  | Some otherNode -> otherNode
  | None ->
    match node with
    | Paragraph (id, children) -> 
      Paragraph (id, (List.map (replaceNode {f}) children))
    | Strong (id, children) ->
      Strong (id, (List.map (replaceNode {f}) children))
    | Text (_, _) -> node;;

最后,我们获得type的函数mapper -> 'a node -> 'a node请注意,这let f: type a.…是组合本地抽象类型和显式多态注释的快捷方式。

完成说明后,(type a)此处需要局部抽象,因为在GADT上进行模式匹配时,只能对抽象类型进行优化。换句话说,我们就需要精确的类型aParagraphStrongText服从不同的等式:a=block段落分支,a=inlineStrongText分公司。

编辑:如何定义一个映射器?

实际上,在定义映射器时,此局部抽象类型的位很重要。例如,定义

let f = function
  | Text ("text2", _) -> Some (Text ("text2", "Dreadful"))
  | _ -> None

产生了一个类型inline node -> inline node optionf,由于在构造匹配Text产生了平等'type_of_scrutinee=inline

为了纠正这一点,需要添加一个本地抽象类型注释,以使类型检查器能够逐分支细化检查对象的类型:

 let f (type a) (node:a) : a node option= match node with
 | Text ("text2", _) -> Some (Text ("text2", "Dreadful"))
 | _ -> None

然后,此f具有正确的类型,并且可以包装在映射器记录中:

let f = { f }

广告:OCaml手册从版本4.06开始详细介绍了所有这些内容。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章