在OCaml中写这个的正确方法是什么?

时间:2013-02-05 12:32:37

标签: ocaml

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

2 个答案:

答案 0 :(得分:8)

正确的方法来移植这个确切的代码片段(我猜,是的 来自this webpage)即将使用 这个FAQ条目中解释的第一类多态性:How to write a function with polymorphic arguments?。请 请阅读FAQ条目,但为了快速参考,这里有一个工作代码示例:

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 printerstring -> '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的唯一方法 type是在字符串上调用参数函数,所以实际上你是 只做返回一个字符串,并调用该函数(继续) 在上面。这可以改写为:

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就属于这种情况,但<{1}}的情况不是 a(多态)参数中的'a。类型系统完全尊重ML(Hindley-Damas-Milner)推理系统的这种限制,不会推断出这种类型,因此拒绝需要它作为错误类型的程序(意思不是“错误的”,而是“我无法证明它是正确的”) )。

这个限制使得推理系统都是可判定的(完整系统F的类型推断是不可判定的(Joe B. Wells))和“principal”(如果你不知道什么是主要类型,请参见this SO discussion;简而言之,这意味着推理引擎总是选择最通用的类​​型),但它也拒绝了类型很好的程序,这是安全类型系统常见的祸害。

大多数ML语言(OCaml,Haskell ......)在某些时候遇到了他们真正希望能够编写的代码的这种限制。对于九十年代的OCaml,在使用该语言编写面向对象编程模式时(参见examples in the manual)。

这就是为什么第一类多态首先在方法类型中引入,然后扩展到记录(一级模块是一个独立的,更新近的添加,也允许这样)。有一个很好的理由为什么它或多或少地局限于某些意义上具有“字段”的“事物”,因为这给出了(必要的)多态性注释的自然方式,并且字段投影是一个很好的地方。 type-checker测试实例化多态值的必要性 - 如果你愿意,这会使理论更简单。

对于OCaml社区这一时期的研究工作,请参见例如Extending ML with Semi-Explicit Higher-Order Polymorphism,Jacques Garrigue和DidierRémy,1997年。开发了其他一流多态的方法(参见第5节(相关工作)) Dider Le Botlan的Recasting MLF概述了文献),以及其他用例,特别是Haskell的ST monad(Lazy Functional State Threads,Simon Peyton Jones和John Launchbury,1994)

答案 1 :(得分:1)

从这一行

| MyInt i -> printerf string_of_int i

编译器推断printerf的第一个参数应该具有类型int -> string,但是从下一行开始

| MyFloat x -> printerf string_of_float x

似乎printerf的第一个参数应该是float -> string。 我想你知道OCaml对于整数和实数有不同的类型......所以printerf类型的这种碰撞会让编译器生病。

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