ExistentialQuantification
這是一種型別系統擴充套件,它允許存在量化的型別,換句話說,具有僅在執行時例項化的型別變數 † 。
存在型別的值類似於面嚮物件語言的抽象基類的引用:你不知道確切的型別包含,但可以約束的類的型別。
data S = forall a. Show a => S a
或等效地,使用 GADT 語法:
{-# LANGUAGE GADTs #-}
data S where
S::Show a => a -> S
存在型別為幾乎異質的容器開啟了大門:如上所述,S
值實際上可以有各種型別,但它們都可以是 show
n,因此你也可以做
instance Show S where
show (S a) = show a -- we rely on (Show a) from the above
現在我們可以建立這樣的物件的集合:
ss = [S 5, S "test", S 3.0]
這也允許我們使用多型行為:
mapM_ print ss
存在感可能非常強大,但請注意,在 Haskell 中它們實際上並不常見。在上面的例子中,你實際上可以用 Show
例項做的就是顯示(duh!)值,即建立一個字串表示。因此,整個 S
型別包含與顯示時所獲得的字串完全相同的資訊。因此,通常最好簡單地立即儲存該字串,特別是因為 Haskell 是懶惰的,因此字串最初只是一個未評估的 thunk。
另一方面,存在會導致一些獨特的問題。例如,型別資訊在存在主義中隱藏的方式。如果你對 S
值進行模式匹配,你將在範圍內包含所包含的型別(更準確地說,它的 Show
例項),但是這個資訊永遠不會轉義它的範圍,因此它變成了一個祕密社會:編譯器除了從外部已知型別的值之外,不會讓任何東西轉義範圍。這可能導致像 Couldn't match type ‘a0’ with ‘()’ ‘a0’ is untouchable
這樣的奇怪錯誤。
† 將此與普通引數多型進行對比,這通常在編譯時解決(允許完全型別擦除)。
存在型別與 Rank-N 型別不同 - 粗略地說,這些擴充套件是彼此雙重的:要實際使用存在型別的值,你需要一個(可能是約束的)多型函式,如示例中的 show
。多型函式是普遍量化的,即它適用於給定類中的任何型別,而存在量化意味著它適用於某種特定型別,這是先驗未知的。如果你有一個多型函式,那就足以傳遞多型函式,比如引數,你需要 {-# LANGUAGE Rank2Types #-}
:
genShowSs :: (∀ x . Show x => x -> String) -> [S] -> [String]
genShowSs f = map (\(S a) -> f a)