4
type t = MyInt of int | MyFloat of float | MyString of string
let foo printerf = function
  | MyInt i -> printerf string_of_int i
  | MyFloat x -> printerf string_of_float x
  | MyString s -> printerf (fun x -> x) s

它报告:

Error: This expression has type float -> string
       but an expression was expected of type int -> string
4

2 回答 2

8

移植这个确切的代码片段(我猜它来自这个网页)的正确方法是使用一流的多态性,如本常见问题解答条目中所述:如何编写具有多态参数的函数?. 请阅读常见问题解答条目,但为了快速参考,这里是工作代码的一个示例:

type t = MyInt of int | MyFloat of float | MyString of string

type 'b printer = { print : 'a . ('a -> string) -> 'a -> 'b }
let foo erf = function
  | MyInt i -> erf.print string_of_int i
  | MyFloat x -> erf.print string_of_float x
  | MyString s -> erf.print (fun x -> x) s

let () = foo
  { print = fun printer data -> print_endline (printer data) }
  (MyString "Hello World!")

但是请注意,您实际上并不需要这种一流的多态性。通过参数化,唯一printer可以对类型数据做的事情'a就是将它传递给打印函数 'a -> string。所以打印机的行为完全取决于它对结果string数据的作用。换句话说,类型与类型'b printer同构string -> 'b,它不会带来其他信息。所以你可以写:

let foo erf = function
  | MyInt i -> erf (string_of_int i)
  | MyFloat x -> erf (string_of_float x)
  | MyString s -> erf s

let () = foo print_endline (MyString "Hello World!")

的类型foo是现在(string -> 'a) -> t -> 'a。这被称为 延续传递风格:摆脱这种类型的唯一方法'a是在字符串上调用参数函数,所以实际上你只是返回一个字符串并调用函数(延续)在上面。这可以重写为:

let foo = function
  | MyInt i -> string_of_int i
  | MyFloat x -> string_of_float x
  | MyString s -> s

let () = print_endline (foo (MyString "Hello World!"))

长话短说:您展示的代码过于复杂,而且它似乎提出的问题被夸大了。这里不需要复杂的类型系统。(但您当然可以找到更好的示例,其中常见问题解答中演示的一流多态性实际上很有用。只是碰巧这个示例......很烂。)

历史和科学背景

问题要求的关于一流多态性的历史性旁白。

'a . ('a -> string) -> 'a -> 'b意思是“为所有人'a('a -> string) -> 'a -> 'b。它是类型变量中的多态类型'a(由'a .位“定义”),并且具有自由变量'b(定义为printer类型的参数)。

foo上面代码的第一个版本中的类型是'b printer -> t -> 'b. 可以理解为对System F类型进行编码

forall 'b . (forall 'a . ('a -> string) -> 'a -> 'b) -> t -> 'b

ML 类型推断算法是推断类型仅限于“前缀多态”,即所有类型变量都在前面量化的类型。这是上述'b类型的情况,但不是(多态)参数'a中量化的。完全尊重 ML (Hindley-Damas-Milner) 推理系统的这种限制的类型系统不会推断出这种类型,因此会拒绝将需要它的程序视为错误类型(意思不是“错误”但“我无法证明它是正确的” )。

这种限制使得推理系统既可判定(完整系统 F 的类型推断是不可判定的(Joe B. Wells))和“主体”(如果您不知道主体类型是什么,请参阅此 SO 讨论;简而言之,这意味着推理引擎总是选择最通用的类​​型),但它也拒绝类型良好的程序,这通常是安全类型系统的祸根。

大多数 ML 语言(OCaml、Haskell ......)在某些时候遇到了他们真正希望能够编写的代码的这种限制。对于 90 年代出现的 OCaml,当时正在使用该语言对面向对象的编程模式进行编码(参见手册中的示例)。

这就是为什么第一类多态性首先在方法类型中引入,后来扩展到记录(第一类模块是一个独立的、最近添加的也允许这样做)。有一个很好的理由为什么这在某种意义上仍然或多或少地局限于具有“字段”的“事物”,因为这为放置(必要的)多态性注释提供了一种自然的方式,而字段投影是一个很好的地方类型检查器来测试实例化多态值的需要——如果你愿意的话,这会使理论更简单。

有关 OCaml 社区这一时期的研究工作,请参见Extending ML with Semi-Explicit Higher-Order Polymorphism , Jacques Garrigue 和 Didier Rémy, 1997。已经开发了其他一流多态性的方法(参见第 5 节(相关著作) ) ,以及其他用例,特别是Haskell 中的 monad ( Lazy Functional State Threads,Simon Peyton Jones 和 John Launchbury,1994) ST

于 2013-02-05T16:34:57.327 回答
1

从这条线

| MyInt i -> printerf string_of_int i

编译器推断 的第一个参数printerf应该有 type int -> string,但从下一行

| MyFloat x -> printerf string_of_float x

似乎第一个论点printerf应该是 a float -> string。我想你知道 OCaml 对整数和实数有不同的类型......所以这种类型的冲突printerf让编译器生病了。

printerf您期望此代码中的类型是什么类型?也许您应该避免使用高阶函数并更直接地实现到字符串的转换?

于 2013-02-05T12:42:11.260 回答