7

如果我为高阶函数指定(我认为)正确的类型,OCaml 编译器会拒绝该函数的第二次使用。

编码

let foo ():string  =
    let f: ('a -> string) -> 'a -> string = fun g v -> g v
    in let h = string_of_int
    in let i = string_of_float
    in let x = f h 23
    in let y = f i 23.0
    in x ^ y

导致以下错误消息

文件“test.ml”,第 6 行,字符 14-15:
错误:此表达式的类型为 float -> string
       但是需要一个 int -> string 类型的表达式

所以第一次使用f似乎将其第一个参数的类型固定为int -> string. 我能理解。但我没有得到的是省略类型限制来f解决问题。

let foo ():string  =
    let f g v = g v
    in let h = string_of_int
    in let i = string_of_float
    in let x = f h 23
    in let y = f i 23.0
    in x ^ y

转移f到全局范围也解决了这个问题:

let f: ('a -> string) -> 'a -> string = fun g v -> g v

let foo ():string  =
  let h = string_of_int
  in let i = string_of_float
  in let x = f h 23
  in let y = f i 23.0
  in x ^ y

为什么第一个示例不编译而后面的示例编译?

4

3 回答 3

9

让我用一个更简单的例子来说明这个问题。

# let cons0 (x : 'a) (y : 'a list) = x :: y;;
val cons0 : 'a -> 'a list -> 'a list = <fun>
# let cons1 (x : 'a) (y : 'a list) = x :: y in cons1 1 [];;
- : int list = [1]
# let cons2 (x : 'a) (y : 'a list) = x :: y in (cons2 1 [], cons2 true []);;
This expression has type bool but is here used with type int
# let cons3 x y = x :: y in (cons3 1 [], cons3 true []);;
- : int list * bool list = ([1], [true])

cons0是一个多态函数定义,在全局范围内定义。对于操作员来说,这只是一个微不足道的包装器::。不出所料,这个定义是有效的。cons1与 几乎相同cons0,只是其范围仅限于in正文中的表达式。范围的变化看起来是无害的,果然,它会进行类型检查。cons3还是同一个函数,没有类型注释,我们可以在in主体中多态地使用它。

那么有什么问题cons2呢?问题是'a: 它是整个顶级短语的范围。定义的短语的语义cons2

for some type 'a, (let cons2 (x : 'a) (y : 'a list) = ... in ...)

由于'a必须与int(due to cons3 1 []) 和bool( due to 兼容,因此cons3 true []不可能实例化'a。因此该短语是错误类型的。

如果您喜欢根据其通常的类型推断算法来考虑 ML 类型,则每个显式用户变量都会在统一算法中引入一组约束。这里的约束是'a = "type of the parameter x"' = "type of the parameter y"。但是范围'a是整个短语,它没有概括为任何内部范围。因此intbool两者最终统一为非广义'a

最近的版本 OCaml 引入了作用域类型变量(如Niki Yoshiuchi 的回答)。在早期版本(≥2.0)中使用本地模块也可以实现相同的效果:

let module M = struct
    let cons2 (x : 'a) (y : 'a list) = x :: y
  end in
(M.cons2 1 [], M.cons2 true []);;

(标准 MLers 注意:这是 OCaml 和 SML 不同的地方。)

于 2010-12-14T20:44:32.157 回答
4

这是一个真正令人头疼的问题,如果它是一个编译器错误,我不会感到惊讶。也就是说,您可以通过显式命名类型来做您想做的事情:

let f (type t) (g: t->string) (v: t) = g v in

来自手册:http ://caml.inria.fr/pub/docs/manual-ocaml/manual021.html#htoc108

编辑:

这也有效:

let f g v:string = g v in

它具有您正在寻找的类型签名:('a -> string) -> 'a -> string

奇怪的是,当您注释参数的类型时它不起作用。

编辑:

多态类型注解有一个特殊的语法:

let f: 'a. ('a -> string)-> 'a -> string = fun g v -> g v in

并且类型必须是多态的:http ://caml.inria.fr/pub/docs/manual-ocaml/manual021.html#toc79

于 2010-12-14T19:50:49.957 回答
-2

作为参考点,等效的 F#

let foo ():string  = 
    let f: ('a -> string) -> 'a -> string = fun g v -> g v 
    let h = string 
    let i = string
    let x = f h 23 
    let y = f i 23.0 
    x ^ y 

编译。

于 2010-12-14T20:46:57.450 回答