常見錯誤訊息
此頁面列出 OCaml 編譯器發出的一些錯誤或警告訊息的快速說明。更詳細的說明通常會在教學的專門章節中提供。
型別錯誤
此運算式的型別為 ... 但在此處與型別 ... 一起使用
當物件的型別與其使用的上下文不相容時,通常會收到這種訊息
# 1 + 2.5;;
Line 1, characters 5-8:
Error: This expression has type float but an expression was expected of type
int
「此運算式的型別為 X 但在此處與型別 Y 一起使用」表示,如果將運算式的內容隔離 (2.5),則其型別會被推斷為 X (float)。但上下文,即周圍的一切 (1 + ...),表示空格預期一個型別為 Y (int) 的運算式,這與 X 不相容。
更令人困擾的是以下訊息
This expression has type my_type but is here used with type my_type
在互動式頂層測試一些型別定義時,經常會發生此錯誤。在 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;;
Line 1, characters 5-6:
Error: This expression has type my_type/1
but an expression was expected of type my_type/2
Hint: The type my_type has been defined multiple times in this
toplevel session. Some toplevel values still refer to old versions
of this type. Did you try to redefine them?
對於編譯器來說,my_type 的第二個定義與第一個定義完全獨立。因此,我們定義了兩個具有相同名稱的型別。「a」是在之前定義的,因此屬於第一個型別,而「b」屬於第二個型別。在此範例中,在 my_type 的最後定義之後重新定義「a」可以解決問題。除非在同一個模組中使用相同的名稱表示相同的型別(強烈不建議),否則這種問題不應該在實際程式中發生。
警告:此選用引數無法刪除
具有選用引數的函式必須至少有一個未標記的引數。例如,這樣是不行的
# let f ?(x = 0) ?(y = 0) = print_int (x + y);;
Line 1, characters 18-23:
Warning 16 [unerasable-optional-argument]: this optional argument cannot be erased.
Line 1, characters 9-14:
Warning 16 [unerasable-optional-argument]: 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>
請參閱 標籤章節,以了解有關使用標籤引數的函式的詳細資訊。
此運算式的型別...包含無法泛化的型別變數
當編譯器到達編譯單元(檔案)的末尾時,物件的完整型別在某些情況下編譯器無法得知,但由於某些原因,它無法保持多型時,就會發生這種情況。範例
# 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}
型別為 '_weak<n>
的資料可能會暫時允許,例如在頂層會話期間。這表示給定物件具有未知的型別,但它不能是任何型別:它不是多型資料。在頂層中,我們的範例會產生以下結果
# let x = ref None;;
val x : '_weak2 option ref = {contents = None}
編譯器告訴我們 x 的型別尚未完全得知。但透過稍後使用 x
,編譯器可以推斷出 x
的型別
# x := Some 0;;
- : unit = ()
現在 x
有一個已知的型別
# x;;
- : int option ref = {contents = Some 0}
模式匹配警告和錯誤
此模式未使用
此警告應視為錯誤,因為沒有理由故意保留此類程式碼。當程式設計師無意中引入了包含所有情況的模式時,可能會發生這種情況,如下所示
# let test_member x tup =
match tup with
| (y, _) | (_, y) when y = x -> true
| _ -> false;;
Line 3, characters 14-20:
Warning 12 [redundant-subpat]: this sub-pattern is unused.
Line 3, characters 5-20:
Warning 57 [ambiguous-var-in-pattern-guard]: Ambiguous or-pattern variables under guard;
variable y appears in different places in different or-pattern alternatives.
Only the first match will be used to evaluate the guard expression.
(See manual section 11.5)
val test_member : 'a -> 'a * 'a -> bool = <fun>
顯然,程式設計師對 OCaml 的模式匹配有所誤解。請記住以下事項
- 案例樹會從左到右線性遍歷。如同 regexp 匹配一樣,沒有回溯。
- 守衛(「when」子句)不是模式的一部分。它只是一個條件,最多會評估一次,並用作跳至下一個匹配案例的最後手段。
- 小寫識別碼(例如上方的「y」)只是名稱,因此它們始終會匹配。
在我們的範例中,現在很清楚的是,只會測試配對的第一個項目。這會產生以下結果
# test_member 1 (1, 0);;
- : bool = true
# test_member 1 (0, 1);;
- : bool = false
此模式匹配不完整
OCaml 的模式匹配可以根據型別來檢查一組模式是否完整。因此,在以下範例中,編譯器不知道「mod」運算子會傳回哪個 int 範圍
let is_even x =
match x mod 2 with
| 0 -> true
| 1 | -1 -> false
一個不需要模式匹配的簡短解決方案是
# let is_even x = x mod 2 = 0;;
val is_even : int -> bool = <fun>
一般而言,這種簡化是不可能的,最好的解決方案是新增一個永遠不會到達的包含所有情況的案例
# let is_even x =
match x mod 2 with
| 0 -> true
| 1 | -1 -> false
| _ -> assert false;;
val is_even : int -> bool = <fun>
重新編譯有效程式時的問題
x.cmi 不是已編譯的介面
重新編譯一些舊程式或從未正確清除的外部來源編譯程式時,可能會收到此錯誤訊息
some_module.cmi is not a compiled interface
這表示根據 OCaml 編譯器的目前版本,some_module.cmi 無效。大多數情況下,移除舊的已編譯檔案 (*.cmi, *.cmo, *.cmx, ...) 並重新編譯足以解決此問題。
警告:字串中的非法反斜線逸出
由於反斜線應該加倍,因此較新版本的 OCaml 會警告您字串中未受保護的反斜線。編譯較舊的程式時可能會顯示此類訊息,並且可以使用 -w x
選項將其關閉。
# "\e\n" (* bad practice *);;
File "_none_", line 1, characters 1-3:
Warning 14 [illegal-backslash]: illegal backslash escape in string.
- : string = "\\e\n"
# "\\e\n" (* good practice *);;
- : string = "\\e\n"