簡體   English   中英

OCaml類型不能一概而論

[英]OCaml type cannot be generalized

我有以下代碼:

type 'v state = {
  visited: string list;
  unvisited: string list;
  value: 'v;}

type ('a,'b) parser =
  | Parser of ('a state -> 'b state list)

type 'a result =
  | Ok of 'a
  | Err

let parseString x = Ok x
let parseInt x = Ok (int_of_string x)
let custom = fun stringToSomething  ->
    Parser (fun { visited; unvisited; value }  ->
            match unvisited with
            | [] -> []
            | next::rest ->
                 (match stringToSomething next with
                  | Ok (nextValue) ->
                      [{
                         visited = (next :: visited);
                         unvisited = rest;
                         value = (value nextValue)
                       }]
                  | Err  -> [])))

let stringp = custom parseString
let intp = custom parseInt

當我嘗試編譯程序時,在custom parseString上出現以下錯誤:

Error: The type of this expression, (string -> '_a, '_a) parser,
         contains type variables that cannot be generalized

這個錯誤是什么意思?

常規類型變量是可以用任何類型替換的變量。 非通用類型變量,也稱為弱類型變量,是可以具體化為一種且僅一種類型的變量。 通常,當值是可變的或作為函數應用程序時,會出現弱類型變量。 通常,類型泛化只能應用於屬於“語法值”類別的表達式,該表達式包括常量,標識符,函數,語法值的元組等 在OCaml中放寬了此通用規則,並且如果所有其他表達式的類型變量出現在協變位置,則也可以將其通用化。 為此,類型系統應該要么查看類型定義並從中推斷出協方差(即,類型不應該是抽象的),要么將類型變量限制為協變類型(即,在類型定義中以+開頭) 。

不可泛型類型可以在模塊結構中使用,但是它們不能逃脫編譯單元,因為這會破壞類型的可靠性(不同的模塊可能將它們具體化為不同的值,並且超出了類型系統的能力)。 由於您的模塊不包含.mli文件,因此默認情況下會導出所有內容。

通常,有四種方法可以解決此問題:

  1. 創建.mli文件,其中非.mli類型的值被隱藏或具體.mli單態類型;

  2. 使用類型約束將類型變量具體化為單態類型;

  3. 使用eta-expansion泛化類型,即將值轉換為語法函數;

  4. 向類型系統證明,通過顯示類型變量是協變的,可以放寬值限制。

這些是一般策略。 在您的情況下,不可能概括化stringp ,因為custom parseString不屬於語法值的一類(它是函數應用程序),並且它是一個表達式,其中類型變量之一是互變的,因為它出現在->類型運算符的左側。 您總是可以向類型系統詢問類型變量的方差。 例如,在不知道任何方差規則的情況下,我們可能會問類型系統:是否確實'a'b是協變的。

type (+'a,+'b) parser =
  | Parser of ('a state -> 'b state list)

協方差推斷算法將計算以下答案:

The 1st type parameter was expected to be covariant,
       but it is injective contravariant.

從類型系統的角度來看,這意味着將stringp定義為副作用可能會訪問類型'a的值(例如,將其存儲在緩存存儲區中),並且在此概括地說,類型變量是不合理的,即會導致分割錯誤。

因此,這里剩下的唯一解決方案是將您的定義擴展到一個函數,這將向類型系統保證每次創建新的解析器時,例如,

let stringp () = custom parseString

或者,或者,不創建類型解析器的值,而只是為用戶提供創建它們的組合器(即, custom函數)。 這樣,他們基本上可以在不需要泛化的情況下即時創建它們),例如,

let parse = custom
let string = parseString

let my_parser ... = 
  parse string >> parse char >> ...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM