因此,這裏涉及兩個概念:let-polymoprhism和價值限制。讓 - 多態性不允許所有非限制值的類型泛化。或者,在不使用雙重否定的情況下,只有在引入了let-binding的情況下,才允許值爲多態。這是一種過度逼近,即它可能不允許有效的程序(有誤報),但它永遠不會允許一個無效的程序(它將保持健全性)。
價值限制是另一個過度逼近,這是必要的,以保持命令式程序的健全性。它不允許非語法值的多態。 OCaml使用這種過度逼近的更精確版本,稱爲relaxed value restriction(實際上允許某些非語法值是多態的)。
不過,讓我先解釋一下什麼是語法值:
通俗地說,句法值是可以不用做任何的計算來計算的表達式,例如,請考慮下面就讓結合:
let f = g x
這裏f
不是一個語法值,因爲爲了獲得需要計算表達式g x
的值。但是,在下面,
let f x = g x
值f
是語法,它會更明顯,如果我們將刪除糖:
let f = fun x -> g x
現在很明顯,即f
是語法,因爲它綁定到lambda表達式。
該值被稱爲句法,因爲它是直接在程序中定義的(在語法中)。基本上,它是一個可以在靜態時間計算的常數值。略更正式,以下值被認爲是語法:
- 常數(即,像整數和浮點文本)
- 構造函數,只有包含其他簡單的值
- 函數聲明,即表達式樂趣或功能,或等效讓結合開始,
let f x = ...
- 形式的let綁定讓VAR =表達式1在表達式2,其中兩個表達式1和表達式2是簡單值
現在,當我們非常確定什麼是語法的時候,讓我們更仔細地看看你的例子。讓我們從賴特的例子開始,實際上是:
let f = (fun x => x) (fun y => y)
,或者通過引入let id = fun x -> x
let f = id id
您可能會看到,那f
這裏不是語法值,雖然id
是語法。但爲了得到f
的值,你需要計算 - 所以這個值是在運行時定義的,而不是在編譯時。
現在,讓我們desugar你的例子:
let x a = let x = (fun y -> y) a in x
==>
let x = fun a -> let x = (fun y -> y) a in x
我們可以看到,x
是句法值,因爲左邊是一個lambda表達式。拉姆達表達式的類型是'a -> 'a
。你可能會問,爲什麼表達式的類型不是'_a -> '_a
。這是因爲值限制只在頂層引入,並且lambda表達式還不是一個值,它是一個表達式。從外行的角度來說,首先,最普遍的Hindley-Milner類型是根據假設推斷的,即沒有副作用,然後通過(放鬆)的價值限制來減弱推斷類型。類型推斷的範圍是一個let
綁定。
這就是所有的理論,有時它不是很明顯,爲什麼一些表達式是很好的類型,而具有相同語義但寫法稍有不同的表達式沒有很好的類型。直覺可能會說,這裏有什麼問題。是的,事實上,let f = id id
是一個結構良好的程序,被類型檢測程序拒絕,並且這是過度近似的示例。如果我們將這個程序轉換爲let f x = id id x
,它會突然變成一個具有通用類型的良好類型的程序,儘管轉換不會改變語義(並且這兩個程序實際上編譯爲相同的機器代碼)。這是一個類型系統的侷限性,它是簡單性和精確性之間的妥協(穩健性不能成爲妥協的一部分 - 類型檢測器必須是合理的)。所以,從後一個例子總是安全的理論來看,這完全不明顯。只是爲了實驗的緣故,讓我們嘗試用你的榜樣玩,並試圖打破程序:
# let x = fun a -> let z = ref None in let x = (fun y -> z := Some y; y) a in x ;;
val x : 'a -> 'a = <fun>
因此,我們增加了一個參考z
這裏,我們正在嘗試存儲的價值,所以,根據不同的應用程序不同的類型,我們應該能夠存儲到不同類型的相同參考值。但是,這是完全不可能的,因爲因爲x
是一個語法值,所以保證每個類型x k
被調用一個新的引用被創建,並且這個引用永遠不會泄漏let-definition的範圍。希望,這有助於:)
來源
2017-02-10 14:23:24
ivg
OCaml沒有使用Wright的論文中描述的值限制,而是使用更復雜的多層算法,其中語法值的概念被非擴展值的概念取代,即沒有可觀察副作用的值。該算法更加精確,可以輸入更多的程序。因此,將Wrights紙直接應用於OCaml可能是一個糟糕的主意。非語法值可以在OCaml中推廣,所以不是所有具有通用類型的值都是語法值。我提供了一個詳細的答案,我試圖把重點放在語法值的一般概念上。 – ivg