【问题标题】:ocaml type over-binding due to specialized recursive use of typeocaml 类型过度绑定,由于专门递归使用类型
【发布时间】:2013-04-10 18:24:52
【问题描述】:

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

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

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

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)

有什么方法可以在不复制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)

【问题讨论】:

    标签: ocaml hindley-milner


    【解决方案1】:

    这种类型的递归在数据类型定义中被称为“非常规”:递归类型'a t 在实例foo t 中被重用,其中foo 与定义中使用的单个变量'a 不同。另一个著名的例子是完全二叉树的类型(正好有 2^n 个叶子):

    type 'a full_tree =
      | Leaf of 'a
      | Node of ('a * 'a) full_tree
    

    操作这些数据类型的递归函数通常受到具有类型推断的语言的单态递归限制。当您进行类型推断时,您必须在对其主体进行类型检查之前猜测递归函数的类型(因为它可能在内部使用)。 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
    

    从 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
    

    你可以在你的例子中做同样的事情。但是,我无法 使用模块中的注释后编译类型 签名,因为它似乎是错误的('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)
    

    【讨论】:

    • 太棒了。谢谢。我让它与let rec map : 'a 'b 'c 'd . ('a -> 'b) -> ('c -> 'd) -> ('a, 'c) t -> ('b, 'd) t = fun f g n -> ...一起工作
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-09
    • 2015-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多