[英]Haskell: Why does GHC not infer type in this typeclass with fundeps?
[英]Why does this Haskell code typecheck with fundeps but produce an untouchable error with type families?
給定一些類型定義:
data A
data B (f :: * -> *)
data X (k :: *)
...和這個類型類:
class C k a | k -> a
...這些(為了最小的例子的目的而高度設計)函數定義類型檢查:
f :: forall f. (forall k. (C k (B f)) => f k) -> A
f _ = undefined
g :: (forall k. (C k (B X)) => X k) -> A
g = f
但是,如果我們使用類型族而不是具有函數依賴性的類:
type family F (k :: *)
...然后等效的函數定義無法進行類型檢查:
f :: forall f. (forall k. (F k ~ B f) => f k) -> A
f _ = undefined
g :: (forall k. (F k ~ B X) => X k) -> A
g = f
• Couldn't match type ‘f0’ with ‘X’
‘f0’ is untouchable
inside the constraints: F k ~ B f0
bound by a type expected by the context:
F k ~ B f0 => f0 k
Expected type: f0 k
Actual type: X k
• In the expression: f
In an equation for ‘g’: g = f
我閱讀了OutsideIn(X)論文的第5.2節,它描述了可觸摸和不可觸摸的類型變量,我有點理解這里發生了什么。 如果我添加一個額外的參數f
是推動的選擇f
內外部的forall
,那么程序typechecks:
f :: forall f a. f a -> (forall k. (F k ~ B f) => f k) -> A
f _ _ = undefined
g :: forall a. X a -> (forall k. (F k ~ B X) => X k) -> A
g = f
但是,在這個特定的例子中讓我特別困惑的是為什么函數依賴具有不同的行為。 我聽過人們在不同時間聲稱像這樣的功能依賴等同於類型族和等式,但這表明實際上並非如此。
在這種情況下,函數依賴提供了哪些信息,允許f
以類型族沒有的方式實例化?
我不知道我是否應該將此作為答案發布,因為它仍然非常浪漫,但我確實認為這是本質上正在發生的事情:
要評估(C k (BX)) => X k
值,您必須為k
選擇具體類型,並指向滿足約束的instance C k (BX)
。 要做到這一點,你必須說出類型類' a
參數的形式為B f
,編譯器可以從中提取f
類型(並在這種情況下發現它是X
) - 重要的是,它可以在實際查找之前執行此操作在實例中 ,這將是f
將變得不可觸及的點。
為了評估(F k ~ BX) => X k
,它有點不同。 在這里你不需要指向一個具體的實例,你只需要保證如果編譯器查找 F k
類型家族,那么這個類型將與BX
類型相同。 但是在實際查找實例之前,編譯器不能在此推斷F k
具有形式B f
,因此也不會使用它來將f
與外部量化參數統一起因為不可觸及。
因此,GHC的行為至少不是完全不合理的。 我仍然不確定它是否應該這樣做。
好的,我有機會玩這個。 有幾個分心:
在Type Family版本中,只有f
的定義給出錯誤'f0' is untouchable
。 (您可以使用AllowAmbiguousTypes
來抑制它;這只是將錯誤推遲到g
出現。)讓我們忽略g
。
然后,如果沒有AllowAmbiguousTypes
,f的錯誤消息會提供更多信息:
• Couldn't match type ‘f0’ with ‘f’
‘f0’ is untouchable
inside the constraints: F k ~ B f0
bound by the type signature for:
f :: F k ~ B f0 => f0 k
‘f’ is a rigid type variable bound by
the type signature for:
f :: forall (f :: * -> *). (forall k. F k ~ B f => f k) -> A
Expected type: f0 k
Actual type: f k
啊哈! rigid type variable
問題。 我猜因為f
是由k
的等式約束修正的,這也是剛性的,因為......
轉到沒有g
的FunDep
版本,我們可以在什么類型調用f
? 嘗試
f (undefined undefined :: X a) -- OK
f (undefined "string" :: X String) -- rejected
f Nothing -- OK
f (Just 'c') -- rejected
拒絕消息(對於X String
示例)是
• Couldn't match type ‘k’ with ‘String’
‘k’ is a rigid type variable bound by
a type expected by the context:
forall k. C k (B X) => X k
Expected type: X k
Actual type: X String
• In the first argument of ‘f’, namely
‘(undefined "string" :: X String)’
注意消息是關於k
, 而不是 f
,它是從FunDep確定的 - 或者是我們能找到合適的k
。
說明
函數f
的簽名表示k
是存在量化/更高等級。 然后我們不能允許關於k
任何類型信息逃逸到周圍的上下文中。 我們不能為k
提供任何(非底部)值,因為它的類型會侵入forall
。
這是一個更簡單的例子:
f2 :: forall f. (forall k. f k) -> A
f2 _ = undefined
f2 Nothing -- OK
f2 (Just 'c') -- rejected rigid type var
因此原始的FunDep
版本編譯是一個分心:它不能有人居住。 (根據我之前的懷疑,這是FunDep
的常見症狀。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.