由于专门的递归使用类型,ocaml类型过度绑定

Mik*_*uel 3 ocaml hindley-milner

我有一个参数化类型,递归地使用自己但具有专门的类型参数,当我实现泛型运算符时,由于处理专用子树的情况,该运算符的类型绑定得太紧.第一个代码示例显示了问题,第二个示例显示了我不想使用的解决方法,因为实际代码有更多的情况,因此以这种方式复制代码是一种维护危险.

这是一个显示问题的最小测试用例:

module Op1 = struct
  type 'a t = A | B  (* 'a is unused but it and the _ below satisfy a sig *)

  let map _ x = match x with
    | A -> A
    | B -> B
end

module type SIG = sig
  type ('a, 'b) t =
    | Leaf  of 'a * 'b
    (* Here a generic ('a, 'b) t contains a specialized ('a, 'a Op1.t) t. *)
    | Inner of 'a * ('a, 'a Op1.t) t * ('a, 'b) t

  val map : ('a -> 'b) -> ('a_t -> 'b_t) -> ('a, 'a_t) t -> ('b, 'b_t) t
end

module Impl : SIG = struct
  type ('a, 'b) t =
    | Leaf  of 'a * 'b
    | Inner of 'a * ('a, 'a Op1.t) t * ('a, 'b) t

  (* Fails signature check:
       Values do not match:
         val map :
           ('a -> 'b) ->
           ('a Op1.t -> 'b Op1.t) -> ('a, 'a Op1.t) t -> ('b, 'b Op1.t) t
       is not included in
         val map :
           ('a -> 'b) -> ('a_t -> 'b_t) -> ('a, 'a_t) t -> ('b, 'b_t) t
   *)
  let rec map f g n = match n with
    | Leaf  (a, b)    -> Leaf  (f a, g b)
    (* possibly because rec call is applied to specialized sub-tree *)
    | Inner (a, x, y) -> Inner (f a, map f (Op1.map f) x, map f g y)
end
Run Code Online (Sandbox Code Playgroud)

这个修改版本Impl.map解决了这个问题,但却带来了维护危险.

  let rec map f g n = match n with
    | Leaf  (a, b)    -> Leaf  (f a, g b)
    | Inner (a, x, y) -> Inner (f a, map_spec f x, map f g y)
  and map_spec f n = match n with
    | Leaf  (a, b)    -> Leaf  (f a, Op1.map f b)
    | Inner (a, x, y) -> Inner (f a, map_spec f x, map_spec f y)
Run Code Online (Sandbox Code Playgroud)

有没有办法让这个工作没有重复的身体let rec map


应用gasche的解决方案产生以下工作代码:

let rec map
    : 'a 'b 'c 'd . ('a -> 'b) -> ('c -> 'd) -> ('a, 'c) t -> ('b, 'd) t
  = fun f g n -> match n with
    | Leaf  (a, b)    -> Leaf  (f a, g b)
    | Inner (a, x, y) -> Inner (f a, map f (Op1.map f) x, map f g y)
Run Code Online (Sandbox Code Playgroud)

gas*_*che 5

数据类型定义中的这种递归方式称为"非常规":递归类型在与定义中使用的单个变量不同'a t的实例foo t中重用.另一个众所周知的例子是完整二叉树的类型(正好有2 ^ n个叶子):foo'a

type 'a full_tree =
  | Leaf of 'a
  | Node of ('a * 'a) full_tree
Run Code Online (Sandbox Code Playgroud)

操作这些数据类型的递归函数通常受到具有类型推断的语言的单态递归限制的影响.当你进行类型推断时,你必须先猜测一个递归函数的类型,然后再对它的主体进行类型检查(因为它可能在里面使用).ML语言通过统一/推理来改进这种猜测,但是只能推断出单态类型.如果你的函数对它自己进行多态使用(它在一个不同的类型上递归地调用它作为输入),那么这是无法推断的(在一般情况下它是不可判定的).

let rec depth = function
| Leaf _ -> 1
| Node t -> 1 + depth t
                      ^
   Error: This expression has type ('a * 'a) full_tree
   but an expression was expected of type 'a full_tree
Run Code Online (Sandbox Code Playgroud)

从3.12开始,OCaml允许使用表单的显式多态注释'a 'b . foo,这意味着forall 'a 'b. foo:

let rec depth : 'a . 'a full_tree -> int = function
| Leaf _ -> 1
| Node t -> 1 + depth t
Run Code Online (Sandbox Code Playgroud)

你可以在你的例子中做同样的事情.但是,在使用模块签名中的注释后,我无法编译该类型,因为它看起来是错误的(这'a_t只是奇怪的).以下是我过去的工作原理:

let rec map : 'a 'b . ('a -> 'b) -> ('a Op1.t -> 'b Op1.t) ->
                      ('a, 'a Op1.t) t -> ('b, 'b Op1.t) t
= fun f g n -> match n with
  | Leaf  (a, b)    -> Leaf  (f a, g b)
  | Inner (a, x, y) -> Inner (f a, map f (Op1.map f) x, map f g y)
Run Code Online (Sandbox Code Playgroud)