[英]Why can't I provide a type within a typeclass instance?
此代碼可以正常工作:
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中做到這一點。)
類型檢查不知道a
在最后一行是一樣的a
在它上面的一個給定的,里面where
你有一個新的范圍條款!
如果要關閉它(獲取新的作用域),則需要啟用ScopedTypeVariables
,您可以通過添加
{-# LANGUAGE ScopedTypeVariables #-}
到源文件的頂部
或在ghci中輸入:set -XScoped…
。
或使用ghci -XScoped… myfile.hs
調用ghci ghci -XScoped… myfile.hs
您是正確的,這與forall
關鍵字有關。 但是,該原因需要一些解釋。 如果您不需要太多解釋,請繼續並跳到最后。 如果仍然需要更多信息,請參見《 GHC手冊》的“ 語法擴展”和“ 其他類型的系統擴展”部分。
Haskell中由類型變量參數化的類型聲明,例如data Heh a = Heh a
,實際上是在創建一個類型級別的函數(類型構造函數),該函數將類型作為輸入並返回新類型。 Heh
的“種類”(一種是我們對類型進行分類的方式,就像類型是對值進行分類的方式)是* -> *
而Int
是種類*
而Heh Int
(應用於Int
類型的Heh
類型構造函數)是以及*
。 因此,將* -> *
應用於*
會得到另一個*
,這是普通類型。
變量,無論是在值級別還是在類型級別,都必須通過某種綁定形式來綁定 ,該綁定形式定義了綁定的范圍(名稱與特定變量的綁定有效的程序文本區域)。 在值級別,我們習慣於無處不在看到這些綁定器:一些示例是函數定義模式中的參數綁定, let
表達式中的綁定以及lambda表達式中的綁定(例如\\x -> x
綁定了在表達式主體內命名x
)。
混亂始於類型級別的變量。 他們似乎沒有粘合劑可言,它們只是存在於類型聲明的中間。 這主要是因為Haskell的類型系統源自Hindley-Milner類型系統,該系統最初根本沒有類型注釋,並且使用了一種可以考慮多種類型的表達式的不同方法。 隨着多態類型的概念變得更加形式化,Hindley-Milner的“多型”值變成了“多態”,並且該理論的句法類型版本包括類型級別的綁定器和對應的價值級別的綁定器,這些綁定器將接受一個類型並返回一個表達式用表達式主體中的類型替換類型變量。 由於這些綁定程序在編譯時已完全解析,因此它們不在類型和值級別語法中。 由於在原始系統中,綁定器只能在類型聲明中的某個位置出現,所以這里沒有歧義。
如果您不遵循所有這些,就不必擔心。 要點是,隨着歷史的發展,隨着基礎概念的發展以及以類型化功能語言實現它們的方式,事情得以解決。
無論如何, forall
是類型級變量粘合劑,有點像一個lambda表達式是一個值級變量粘合劑。 如果沒有它,則假定在每個類型級別表達式的開頭都存在一個隱式的forall
,它綁定了所有自由類型變量。 顯式綁定類型變量可以使它們更清楚地表明它們是“通用量化的”(如果您不熟悉通用量化的概念,請參見一階邏輯簡介),並且還提供了進行“現有量化的”可能性”以及排名較高的多態類型變量(具有RankNTypes
擴展名)。 這似乎很奇怪,因為存在量化通常是通過“存在”活頁夾來表示的,但是如果您在data
類型聲明中放入forall a
,則不會將a
本身納入范圍內(例如, data Foo b = forall a. Foo (a, a -> b)
)與對a
的存在量化a
相同的效果(當然,假設您啟用ExistentialQuantification
擴展)。
但是,您真正關心的是如何將類型變量量化的范圍擴大到整個instance
聲明。 這就是ScopedTypeVariables
擴展的用途。 正如我之前說過的,Hindley-Milner類型系統最初沒有使用綁定和自由類型變量(存在類型方案(或多型)和單型,在邏輯解析算法中發揮作用)的概念,甚至沒有使用類型注釋。 首次添加類型注釋時,它們表示“未知單型”,僅指被注釋的特定表達式,而不是諸如forall
或lambda的作用域綁定構造。 因此,如果相同的名稱在類型環境中不止一次出現,則它們將需要重命名。 通過打開ScopedTypeVariables
,在綁定相同名稱的顯式forall
綁定范圍內表示的任何類型變量都不再是未知的單一類型變量(即錯誤消息中的“剛性類型變量”),而是對綁定類型變量的引用在最接近的封閉式活頁夾中具有相同名稱的名稱。
無論如何,這是解決難題所需的最終結果:
{-# 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
是活動的,則參照a
在上類型的注釋a1
指的是相同的a
中forall
結合。
如果您查看文檔,則會發現還有一些綁定類型變量的語法構造。 在這種情況下, forall
實際上不是必需的,因為在頭部的類和實例變量class
和instance
聲明自動綁定中相同的方式的類型變量為forall
當擴展被激活一樣。 但是,如果您有一個帶有類型聲明的獨立函數,而該聲明需要在函數的整個范圍內進行作用域定義,則需要了解如何使用forall
來獲得所需的類型變量作用域。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.