【问题标题】:OCaml type cannot be generalizedOCaml 类型不能泛化
【发布时间】:2016-11-05 14:34:01
【问题描述】:

我有以下代码:

type 'v state = {
  visited: string list;
  unvisited: string list;
  value: 'v;}

type ('a,'b) parser =
  | Parser of ('a state -> 'b state list)

type 'a result =
  | Ok of 'a
  | Err

let parseString x = Ok x
let parseInt x = Ok (int_of_string x)
let custom = fun stringToSomething  ->
    Parser (fun { visited; unvisited; value }  ->
            match unvisited with
            | [] -> []
            | next::rest ->
                 (match stringToSomething next with
                  | Ok (nextValue) ->
                      [{
                         visited = (next :: visited);
                         unvisited = rest;
                         value = (value nextValue)
                       }]
                  | Err  -> [])))

let stringp = custom parseString
let intp = custom parseInt

当我尝试编译我的程序时,custom parseString 出现以下错误:

Error: The type of this expression, (string -> '_a, '_a) parser,
         contains type variables that cannot be generalized

这个错误是什么意思?

【问题讨论】:

    标签: ocaml


    【解决方案1】:

    通用类型变量是可以用任何类型替换的变量。非一般类型变量,也称为弱类型变量,是可以具体化为一种且仅一种类型的变量。通常,当一个值是可变的或它是一个函数应用程序时,就会出现弱类型变量。一般来说,类型泛化只能应用于属于“句法值”类的表达式,包括常量、标识符、函数、句法值的元组等。这个通用规则是OCaml中的relaxed,所有其他表达式的类型变量如果出现在协变位置也可以泛化。为此,类型系统应该查看类型定义并从中推断出协变(即,类型不应该是抽象的),或者类型变量应该被限制为协变类型(即,在类型中以 + 为前缀定义)。

    不可泛化的类型在模块结构中是可以的,但它们不能逃脱编译单元,因为这会破坏类型的健全性(不同的模块可能会将它们具体化为不同的值,并且超出了类型系统的能力来防止这种情况发生)。由于您的模块不包含 .mli 文件,因此默认情况下会导出所有内容。

    一般来说,解决问题的方法有4种:

    1. 创建.mli文件,其中非泛化类型的值要么隐藏要么具体化为单态类型;

    2. 使用类型约束将类型变量具体化为单态类型;

    3. 使用 eta-expansion 泛化类型,即将值转换为句法函数;

    4. 向类型系统证明,通过显示类型变量是协变的,可以放宽值限制。

    这些是一般策略。在您的情况下,不可能概括 stringp,因为 custom parseString 不属于一类语法值(它是一个函数应用程序),它是一个表达式,其中一个类型变量是逆变,因为它出现在 -> 类型运算符的左侧。您总是可以向类型系统询问类型变量的方差。例如,在不知道任何方差规则的情况下,我们可能会问类型系统:'a'b 是否是协变的。

    type (+'a,+'b) parser =
      | Parser of ('a state -> 'b state list)
    

    协方差推理算法将计算出以下答案:

    The 1st type parameter was expected to be covariant,
           but it is injective contravariant.
    

    从类型系统的角度来看,这意味着stringp作为副作用的定义可以访问'a类型的值(例如,存储在缓存存储中),这里概括类型变量不健全,即会导致分段错误。

    所以,这里剩下的唯一解决方案是将您的定义扩展为一个函数,这将向类型系统保证,每次创建新的解析器时,例如,

    let stringp () = custom parseString
    

    或者,或者,不创建类型解析器的值,而只是为用户提供组合器来创建它们(即custom 函数)。这样他们基本上可以在不需要泛化的上下文中动态创建它们),例如,

    let parse = custom
    let string = parseString
    
    let my_parser ... = 
      parse string >> parse char >> ...
    

    【讨论】:

    • 哇,感谢您的好评!我自己永远也想不通。采用组合器方法。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2019-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多