常见的错误信息
这一章给大家一个关于OCaml编译器的一些错误或者警告信息的简要解释。详尽解释在本教程的 相应章节可以找到。
类型错误
This expression has type ... but is here used with type ...
当一个对象的类型与上下文要求的不匹配,你就会看到这个信息:
# 1 + 2.5;;
Error: This expression has type float but an expression was expected of type
int
"This expression has type X but is here used with type Y" 的意思是 如果一个表达式的内容是隔离的 (2.5),那么它的类型可以推导成 X (float)。但是上下文 告诉我们 (1 + ...) 应该是类型 Y (int) 的,因而与 X 不匹配。
下面的信息更加糟糕:
This expression has type my_type but is here used with type my_type
这个错误往往发生在一些toplevel中的类型定义上。在OCaml,重新定义一个已经存在的类型 是完全合法的。看看下面这个会话:
# type my_type = A | B;;
type my_type = A | B
# let a = A;;
val a : my_type = A
# type my_type = A | B;;
type my_type = A | B
# let b = B;;
val b : my_type = B
# a = b;;
Error: This expression has type my_type/1
but an expression was expected of type my_type/2
Line 1, characters 0-20:
Definition of type my_type/1
Line 1, characters 0-20:
Definition of type my_type/2
对于编译器来说第二个定义是完全独立于第一个的。所以我们是定义了两个相同名称的类型。
由于a
在早前就定义了,因此它是属于第一个类型。但b
是属于第二个。在这个例子中,可以
重新定义a
使得它是属于第二个类型的。这个错误是不应该在实际编程中碰到的,除非你在
同一个模块用同一个名字定义不同类型,这显然是自找麻烦。
警告: This optional argument cannot be erased
带可选参数的函数至少有一个未标签的参数。下面这个例子是有警告的:
# let f ?(x = 0) ?(y = 0) = print_int (x + y);;
Warning 16: this optional argument cannot be erased.
val f : ?x:int -> ?y:int -> unit = <fun>
这个警告只要在最后加上unit
就可以解决:
# let f ?(x = 0) ?(y = 0) () = print_int (x + y);;
val f : ?x:int -> ?y:int -> unit -> unit = <fun>
更多关于标签化参数请参见 标签 一章。
The type of this expression... contains type variables that cannot be generalized
当某些情况下,编译器在到了编译单元(文件)结尾时,也无法得知类型的整个定义,并且也无法 是多态时,这个警告会产生:
# let x = ref None;;
val x : '_weak1 option ref = {contents = None}
这会引起下面的编译信息:
The type of this expression, '_a option ref,
contains type variables that cannot be generalized
解决方案:直接告诉编译器你的变量类型:
# let x : string option ref = ref None;;
val x : string option ref = {contents = None}
或者:
# let x = ref (None : string option);;
val x : string option ref = {contents = None}
'_a
的数据类型在toplevel有可能会暂时被允许。它代表这是某个类型,但它
不是所有类型,它不是多态的(译注:也就是说它不是真的多态的,只是toplevel中的一个占位表示而已)。
在toplevel,我们的例子有下面的结果:
# let x = ref None;;
val x : '_weak2 option ref = {contents = None}
编译器告诉我们x
的类型还不完全知道,但是随着x
的使用,编译器可以推导出它的类型:
# x := Some 0;;
- : unit = ()
现在 x
的类型是已知的:
# x;;
- : int option ref = {contents = Some 0}
更多细节在 OCaml FAQ。
模式匹配警告和错误
This pattern is unused
这个警告其实是一个错误,因为这种代码没理由存在。这当程序员粗心地引入一个匹配所有的模式 是会产生,如下:
# let test_member x tup =
match tup with
| (y, _) | (_, y) when y = x -> true
| _ -> false;;
Warning 12: this sub-pattern is unused.
Line 3, characters 4-19:
Warning 57: Ambiguous or-pattern variables under guard;
variable y may match different arguments. (See manual section 9.5)
val test_member : 'a -> 'a * 'a -> bool = <fun>
显然,这里程序员完全曲解了OCaml的模式匹配。记住以下几点:
- 模式匹配的遍历是线性的,由上至下,从左到右。回溯是根本不存在的。
- 一个guard 不是模式的一部分。它只是一个条件语句,并且最多使用一次。它只是让你跳到下一个模式的一个手段。
- 小写的名字只是名字,它们会匹配任何东西的。
在我们的例子中,显然只有第一个模式才被匹配,这导致下面的结果:
# test_member 1 (1, 0);;
- : bool = true
# test_member 1 (0, 1);;
- : bool = false
This pattern-matching is not exhaustive
OCaml's pattern matching can check whether a set of patterns is exhaustive or not, based on the type only. So in the following example, the compiler doesn't know what range of ints the "mod" operator would return:
# let is_even x =
match x mod 2 with
| 0 -> true
| 1 | -1 -> false;;
Warning 8: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
2
val is_even : int -> bool = <fun>
A short solution without pattern matching would be:
# let is_even x = x mod 2 = 0;;
val is_even : int -> bool = <fun>
In general, that kind of simplification is not possible and the best solution is to add a catch-all case which should never be reached:
# let is_even x =
match x mod 2 with
| 0 -> true
| 1 | -1 -> false
| _ -> assert false;;
val is_even : int -> bool = <fun>
Problems recompiling valid programs
x.cmi is not a compiled interface
When recompiling some old program or compiling a program from an external source that was not cleaned properly, it is possible to get this error message:
some_module.cmi is not a compiled interface
It means that some_module.cmi is not valid according to the current version of the OCaml compiler. Most of the time, removing the old compiled files (*.cmi, *.cmo, *.cmx, ...) and recompiling is sufficient to solve this problem.
Warning: Illegal backslash escape in string
Recent versions of OCaml warn you against unprotected backslashes in
strings since they should be doubled. Such a message may be displayed
when compiling an older program, and can be turned off with the -w x
option.
# "\e\n" (* bad practice *);;
Warning 14: illegal backslash escape in string.
- : string = "\\e\n"
# "\\e\n" (* good practice *);;
- : string = "\\e\n"