高阶函数的类型

发布于 2024-10-07 18:24:08 字数 1103 浏览 10 评论 0原文

如果我为高阶函数指定(我认为)正确的类型,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

导致以下错误消息

File "test.ml", line 6, characters 14-15:
Error: This expression has type float -> string
       but an expression was expected of type int -> string

因此 f 的第一次使用似乎将其第一个参数的类型固定为 int ->;字符串。我能理解这一点。但我不明白的是,省略 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

为什么第一个示例无法编译,而后面的示例可以编译?

if I specify the (I think) correct type for a high order function the OCaml compiler rejects the second usage of that function.

The code

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

leads to the following error message

File "test.ml", line 6, characters 14-15:
Error: This expression has type float -> string
       but an expression was expected of type int -> string

So the first usage of f seems to fix the type of its first parameter to int -> string. I could understand that. But what I don't get is that omitting the type restriction on f fixes the problem.

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

And moving f to global scope fixes the problem, too:

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

Why is it that the first example does not compile while the later ones do?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

思念满溢 2024-10-14 18:24:08

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

# 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 是一个多态函数定义,在全局范围内定义。它只是 :: 运算符的一个简单包装。毫不奇怪,这个定义是有效的。 cons1cons0 几乎相同,只是其范围仅限于 in 主体中的表达式。范围的改变看起来无害,而且果然,它进行了类型检查。 cons3 又是相同的函数,没有类型注释,我们可以在 in 主体中多态地使用它。

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

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

因为 'a 必须与 int 兼容(由于 cons3 1 [])并使用 bool (由于 cons3 true [],不可能实例化 'a。因此该短语是错误的- 。

如果您喜欢根据其通常的类型推断算法来考虑 ML 类型,则每个显式用户变量都会在统一算法中引入一组约束,这里的约束是 'a =“参数 x 的类型”' =“参数y的类型”。但是'a的范围是整个短语,它没有在任何内部范围内推广。 code>int 和 bool 最终都统一为非通用的 '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 不同的地方。)

Let me use a simpler example that illustrates the issue.

# 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 is a polymorphic function definition, defined at global scope. It's just a trivial wrapper to the :: operator. Unsurprisingly, the definition works. cons1 is almost the same as cons0, except that its scope is limited to the expression in the in body. The change of scope looks innocuous, and sure enough, it typechecks. cons3 is again the same function, with no type annotation, and we can use it polymorphically in the in body.

So what's wrong with cons2? The problem is the scope of 'a: it's the whole toplevel phrase. The semantics of the phrase that defines cons2 is

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

Since 'a must be compatible with int (due to cons3 1 []) and with bool (due to cons3 true [], there is no possible instantiation of 'a. Therefore the phrase is ill-typed.

If you like to think about ML typing in terms of its usual type inference algorithm, each explicit user variable introduces a set of constraints in the unification algorithm. Here the constraints are 'a = "type of the parameter x" and ' = "type of the parameter y". But the scope of 'a is the whole phrase, it's not generalized in any inner scope. So int and bool both end up unified to the non-generalized 'a.

Recent versions OCaml introduce scoped type variables (as in Niki Yoshiuchi's answer). The same effect could have been achieved with a local module in earlier versions (≥2.0):

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

(Standard MLers note: this is one place where OCaml and SML differ.)

看透却不说透 2024-10-14 18:24:08

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

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

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

编辑:

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

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

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

This is a real head-scratcher and I wouldn't be surprised if it's a compiler bug. That said, you can do what you want by explicitly naming the type:

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

From the manual: http://caml.inria.fr/pub/docs/manual-ocaml/manual021.html#htoc108

Edit:

This also works:

let f g v:string = g v in

which has the type signature you are looking for: ('a -> string) -> 'a -> string

Strange that it doesn't work when you annotate the type of the arguments.

EDIT:

Polymorphic type annotations have a special syntax:

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

And the type is required to be polymorphic: http://caml.inria.fr/pub/docs/manual-ocaml/manual021.html#toc79

坏尐絯 2024-10-14 18:24:08

作为参考点,

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 

可以编译等效的 F#。

As a reference point, the equivalent 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 

compiles.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文