![](/img/trans.png)
[英]Why can't the type of id be specialised to (forall a. a -> a) -> (forall b. b -> b)?
[英]Why is forall a. a not considered a subtype of Int while I can use an expression of type forall a. a anywhere one of type Int is expected?
考慮以下一對函數定義,它們傳遞類型檢查器:
a :: forall a. a
a = undefined
b :: Int
b = a
即表達類型forall a. a
forall a. a
可以在期望Int
類型之一的地方使用。 這在我看來很像子類型,但據稱Haskell的類型系統缺少子類型。 這些形式的可替代性有何不同?
這個問題不是特定於forall a. a
forall a. a
。 其他例子包括:
id :: forall a. a -> a
id x = x
idInt :: Int -> Int
idInt = id
在類型化的lambda calculi中,我們有類型關系,通常表示為:
或者在Haskell中表示為::
。 通常,關系是“多對多”,因此類型可以包含多個值,並且值可以有多種類型。
特別是在多態類型系統中,值可以具有多種類型。 例如
map :: (a -> b) -> [a] -> [b]
但是也
map :: (Int -> Int) -> [Int] -> [Int].
在這種類型系統中,(有時)可以定義關於類型的關系,其含義是“比通用類型更多”, 類型順序 。 如果t ⊑ s
則t
比s
更通用,這意味着如果M : t
也是M : s
,並且這種類型系統的輸入規則允許准確地推斷出。 或者我們說s
是t
。 所以在這個意義上, 類型有一個子類型關系。
但是,當我們談論面向對象語言中的子類型時,我們通常意味着名義上的子類型 ,也就是說,我們聲明哪些類型是什么的子類型,就像我們定義類繼承時一樣。 在Haskell中,它是類型的屬性,獨立於任何聲明。 例如,任何類型都是forall a . a
forall a . a
。
對於允許類型推斷並且是大多數函數式語言的基礎的Hindley-Milner類型系統 ,存在主體類型的概念:如果表達式M
具有(任何)類型,那么它也具有其主要類型,並且主要類型是所有可能類型的M
的最一般類型。 關鍵特征是HM類型推斷算法總是找到最通用的類型。 因此,最常見的推斷主體類型可以專用於任何有效類型的M
有了這樣的問題,我會退一步說,從根本上說,構成Haskell設計基礎的數學理論是沒有子類型概念的System F變體。
是的,可以查看Haskell的表面語法,並注意到有些情況就像你提出的那樣,某些類型T
的表達式可以在任何需要T'
上下文中使用。 但這並不會出現,因為Haskell旨在支持子類型。 相反,它出現的事實是,Haskell被設計為比系統F的忠實渲染更加用戶友好。
在這種情況下,它與以下事實有關:類型級量詞通常不是在Haskell代碼中顯式編寫的,類型級lambda和應用程序永遠不會 。 如果你看一下類型forall a. a
forall a. a
從系統F角度,可替代性進入Int
語境消失。 a :: forall a. a
a :: forall a. a
是類型級別函數,不能在期望Int
的上下文中使用 - 您需要首先將它應用於Int
以獲取a Int :: Int
,然后您可以在Int
上下文中實際使用它。 Haskell的語法以用戶友好的名義隱藏,但它存在於基礎理論中。
簡而言之,雖然您可以通過將可以將哪些表達式類型替換為哪種上下文類型並且證明存在某種加密子類型關系來分析Haskell,但它只是沒有成效,因為它會產生與設計當前游泳相關的分析。 它不是技術問題,而是意圖和其他人為因素。
你是正確的類型forall a. a
類型的值forall a. a
forall a. a
可以在期望Int
任何地方使用,這意味着兩種類型之間的子類型關系。 上面的其他答案試圖說服你,這種“多態多於”的關系不是子類型。 然而,雖然它與典型的面向對象語言中的子類型形式肯定不同,但這並不意味着“更多 - 多態”的關系不能被視為(不同的)子類型形式。 實際上,多態類型系統的一些形式化在它們的子類型關系中精確地建模了這種關系。 例如,在Odersky和Läufer的論文“將類型注釋投入工作”中的類型系統就是這種情況。
通過:: a
我們的意思是“任何類型”,但不是子類型。 a
可以是Int
,或Bool
,或IO (Maybe Ordering)
,但沒有特別的。 a
不是一個完全類型,而是一個類型變量。
假設我們有這樣的函數:
id x = x
編譯器理解我們的參數x
沒有特定的類型。 我們可以使用x
任何類型,只要它等同於id的任何類型。 所以,我們寫簽名如下:
-- /- Any type in...
-- | /- ...same type out.
-- V V
id :: a -> a
請記住,類型以Haskell中的大寫字母開頭。 這不是一個類型:它是一個類型變量!
我們使用多態,因為它更容易實現。 例如,合成是一個有用的想法:
(>>>) :: (a -> b) -> (b -> c) -> (a -> c)
(>>>) f g a = g (f a)
所以我們可以這樣寫:
plusOneTimesFive :: Int -> Int
plusOneTimesFive = (+1) >>> (* 5)
reverseHead :: [Bool] -> Bool
reverseHead = reverse >>> head
但是,如果我們必須像這樣編寫每種類型:
(>>>) :: (Bool -> Int) -> (Int -> String) -> (Bool -> String)
(>>>) f g a = g (f a)
(>>>') :: (Ordering -> Double) -> (Double -> IO ()) -> (Ordering -> IO ())
(>>>') f g a = g (f a)
(>>>'') :: (Int -> Int) -> (Int -> Bool) -> (Int -> Bool)
(>>>'') f g a = g (f a)
-- ...and so on.
那只是愚蠢的。
所以編譯器使用類型統一來推斷類型,如下所示:
假設我把它輸入GHCi。 為簡單起見,我們在Int
說6
。
id 6
編譯器認為:“ id :: a -> a
,它正在傳遞一個Int
,所以a = Int
,所以id 6 :: Int
。
這不是子類型。 可以使用類型類捕獲子類型,但這是基本的多態性。
它不是子類型,它是類型統一!
a :: forall a. a
a = undefined
b :: Int
b = a
在b = a
,我們將b
和a
約束為相同的類型,因此編譯器會檢查是否可能。 a
有類型forall a. a
forall a. a
,根據定義,它與每個類型統一,因此編譯器會提出我們的約束。
類型統一還允許我們執行以下操作:
f :: (a -> Int) -> a
g :: (String -> b) -> b
h :: String -> Int
h = f g
通過統一, f :: (a -> Int) -> a
表示g
必須有類型a -> Int
,這意味着a -> Int
必須與(String -> b) -> b
統一,所以b
必須b
必須是Int
,這給g
的具體類型(String -> Int) -> Int
,這意味着a
是String -> Int
。
a -> Int
和(String -> b) -> b
都不是另一個的子類型,但它們可以統一為(String -> Int) -> Int
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.