2016-03-08 163 views
1

此代碼工作正常預期:爲什麼我不能在類型類實例中提供一個類型?

data Heh a = Heh a 
instance (Eq a) => Eq (Heh a) where 
    (Heh a1) == (Heh a2) = a1 == a2 

然而,這給出了一個錯誤:

data Heh a = Heh a 
instance (Eq a) => Eq (Heh a) where 
    (Heh a1) == (Heh a2) = (a1 :: a) == a2 
    -- Error: Couldn't match expected type ‘a1’ with actual type ‘a’ 
    --  ... 

唯一的變化是增加了:: a

但是,爲什麼會失敗?是不是a1的確是a?還有什麼可能呢?

這如何涉及到forall?我理解Haskell有一個forall關鍵字來處理這個問題。 (順便說一句,如果直到GHC 8.0才能實現,請隨時在即將到來的GHC 8.0中讓我知道該怎麼做。)

+0

是,涉及到'forall'因爲這個關鍵字明確地標誌着一個範圍 - 但我仍然認爲,範圍只標明一個函數體內,而不是where子句,但我對'forall'沒有專家。 – epsilonhalbe

+1

@ epsilonhalbe對你有正確的答案;您需要'forall'和'ScopedTypeVariables'擴展名的組合。顯式'forall'還有其他用途(通過其他擴展),比如創建存在類型,或者只是爲了明確。 –

+0

我推薦*總是*使用'ScopedTypeVariables'。 – dfeuer

回答

3

你是正確的,因爲決議,這同forall關鍵字做打電話ghci的。不過,這個原因需要一些解釋。如果你不關心那麼多的解釋,那就直接跳到最後。如果你還想要更多,請參閱GHC手冊的Syntax ExtensionsOther Type System Extensions部分。

Haskell中由類型變量(例如data Heh a = Heh a)參數化的類型聲明本質上是創建一個類型級函數(類型構造函數),它將一個類型作爲輸入並返回一個新類型。該的Heh「種」(一種是我們如何劃分類型,如類型是如何分類的值)爲* -> *,而Int是一種*Heh Int(適用於類型IntHeh類型構造函數)是一種*以及。因此,適用於*一個* -> *給出了另一個*,這是那種正常的類型。

變量,無論是在值級別或類型級別,必須一些結合形式,它定義了程序文本名稱的特定變量是在其中結合的範圍(區域結合效果)的約束力。在價值層面,我們已經習慣了看到這些粘合劑所有的地方:一些例子在功能定義模式參數綁定,在let表達式的綁定,並在lambda表達式的綁定(如\x -> x結合中的名稱x表達的主體)。

混亂始於型級變量;他們似乎根本沒有綁定,他們只是在類型聲明的中間。這很大程度上是因爲Haskell的類型系統是從Hindley-Milner類型系統開始下降的,而Hindley-Milner類型系統根本不具有類型註釋,並且對於可能採用多種類型的表達式採用了不同的思維方式。多態類型的概念變得更加形式化,「polytyped」從辛德米爾納值成了「多態」和理論的語法類型版本包含的類型級粘合劑和相應的價值層次的粘合劑,將採取和回報類型變量的表達式主體中的類型被替換的表達式。因爲這些綁定器在編譯時被完全解析,所以它們被排除在類型和值級別的語法之外。由於在原始系統中,活頁夾只能在類型聲明中的一個位置出現,因此不存在歧義。

如果你沒有按照這一切,不用擔心它;要點是,事情的發展是出於歷史原因,因爲基本概念以及它們在類型化函數式語言中實現的方式不斷髮展。

反正forall是類型級變量粘合劑,有點像一個lambda表達式是一個值級變量粘合劑。沒有它,假設在每個綁定所有自由類型變量的類型級表達式的開始處有一個隱含的forall。明確地綁定類型變量使得它們更「清楚」,它們是「普遍量化的」(如果你不熟悉普遍量化的概念,請參閱一階邏輯的介紹),並且也打開了「存在量化」的可能性「和更高排名的多態類型變量(擴展名爲RankNTypes)。這似乎很奇怪,因爲存在量化通常表示通過「存在」的粘合劑,但如果你把一個forall adata類型聲明不把a進入活動範圍本身(例如data Foo b = forall a. Foo (a, a -> b)),你得到相同的效果作爲存在量化a(假設您啓用ExistentialQuantification擴展,當然)。

但是你真正關心的是如何提高你的類型變量量化到整個instance聲明的範圍;這是ScopedTypeVariables擴展的用途。正如我之前所說的,Hindley-Milner類型系統最初並沒有使用綁定和自由類型變量的概念(有邏輯解析算法中扮演角色的類型方案(或多類型)和monotypes)甚至類型註釋。當首先加入類型註釋,它們代表「未知monotypes」和稱爲剛好在所述特定表達被註釋而不是一個範圍的結合構造像forall或λ。因此,如果同一個名稱在類型環境中出現不止一次,則它們將被重命名。通過打開ScopedTypeVariables,在明確綁定相同名稱的綁定範圍內表達的任何類型變量不再是未知單形變量(即來自錯誤消息的「剛性類型變量」),而是引用綁定類型變量該名稱最近的封閉類型聯編程序中的同名名稱。

總之,這裏的解決您的難題所需要的最終結果是:

{-# LANGUAGE ScopedTypeVariables #-} 

data Heh a = Heh a 
instance forall a. (Eq a) => Eq (Heh a) where 
    (Heh a1) == (Heh a2) = (a1 :: a) == a2 

forall在實例聲明的範圍遍及實例聲明延伸,因爲ScopedTypeVariables是積極的,在提及aa1上的類型註釋是指與forall中的相同的a綁定。

如果你看的文檔,你會發現有結合類型的變量,以及一些其他的語法結構。在這種情況下,forall實際上不是必需的,因爲在classinstance聲明頭部類和實例變量自動綁定中相同的方式的類型變量爲forall確實當擴展是有效的。但是,如果你有需要跨功能的整個身體先限定類型聲明一個獨立的功能,你需要知道如何使用forall得到你想要的類型變量的作用域。

+0

這個工程即使沒有'forall'關鍵字,只要'ScopedTypeVariables'處於活動狀態。我不確定這是故意還是錯誤,但確實如此。 – dfeuer

+2

我注意到在測試代碼的同時我寫了答案;不過,我幾乎找不到原因並更新了答案。 –

+0

我還有兩個問題:你做了什麼,你是怎麼做到的? :)所有這些知識......令人驚歎!最後的問題:Haskell專業程序員有多少比例知道你寫的內容的一半?這是每個專業Haskell程序員最終應該學習的東西嗎? – haskellHQ

8

類型檢查器不知道最後的a線是相同它上面的一個給定的a,你有一個新的範圍where子句中!

如果你想要把它們關掉(獲得新的範圍),你需要通過添加

{-# LANGUAGE ScopedTypeVariables #-} 

到您的源文件

的頂部,使ScopedTypeVariables,你可以做或鍵入:set -XScoped…內ghci中。

或與ghci -XScoped… myfile.hs

相關問題