[英]What does `~` (tilde) mean in an instance context, and why is it necessary to resolve overlap in some cases?
請考慮以下代碼段:
class D u a where printD :: u -> a -> String
instance D a a where printD _ _ = "Same type instance."
instance {-# overlapping #-} D u (f x) where printD _ _ = "Instance with a type constructor."
這就是它的工作原理:
λ printD 1 'a'
...
...No instance for (D Integer Char)...
...
λ printD 1 1
"Same type instance."
λ printD [1] [1]
...
...Overlapping instances for D [Integer] [Integer]
...
λ printD [1] ['a']
"Instance with a type constructor."
請注意,盡管為此提供了編譯指示,但仍未解析重疊實例。
得出以下調整后的定義需要一些猜測:
class D' u a where printD' :: u -> a -> String
instance (u ~ a) => D' u a where printD' _ _ = "Same type instance."
instance {-# overlapping #-} D' u (f x) where printD' _ _ = "Instance with a type constructor."
它的工作原理如我所預期的那樣:
λ printD' 1 'a'
...
...No instance for (Num Char)...
...
λ printD' 1 1
"Same type instance."
λ printD' [1] [1]
"Instance with a type constructor."
λ printD' [1] ['a']
"Instance with a type constructor."
我很難理解這里發生的事情。 有解釋嗎?
特別是,我可以提出兩個不同的問題:
但是,如果問題是相關的,也許一個統一的理論可以更好地解釋這個案例。
PS關於重復/重復投票我知道~
表示類型相等,我有意識地使用它來獲得我需要的行為(特別是, printD' 1 'a'
不匹配) 。 它幾乎沒有解釋任何與我提出的具體情況有關的情況,其中說明類型相等的兩種方式( ~
和instance D aa
)導致兩種微妙不同的行為。
注意我用ghc
8.4.3
和8.6.0.20180810
測試了上面的片段
第一:在實例選擇期間只有實例頭很重要: =>
左邊的內容無關緊要。 因此, instance D aa
阻止選擇,除非它們相等; instance ... => D ua
總是可以選擇instance ... => D ua
。
現在,重疊編譯指示僅在一個實例已經比另一個實例更“特定”時才起作用。 在這種情況下,“特定”表示“如果存在可以將實例頭A
實例化為實例頭B
的類型變量的替換,則B
比A
更具體”。 在
instance D a a
instance {-# OVERLAPPING #-} D u (f x)
既沒有比另一個更具體,因為沒有替代a := ?
這使得D aa
成為D u (fx)
,也沒有任何替代u := ?; f := ?; x := x
u := ?; f := ?; x := x
u := ?; f := ?; x := x
使D u (fx)
成為D aa
。 {-# OVERLAPPING #-}
pragma什么都不做(至少與問題有關)。 因此,在解析約束D [Integer] [Integer]
,編譯器會發現兩個實例都是候選實例,既不比另一實例更具體,又會出錯。
在
instance (u ~ a) => D u a
instance {-# OVERLAPPING #-} D u (f x)
第二個實例比第一個實例更具體,因為第一個實例可以用u := u; a := fx
實例化u := u; a := fx
u := u; a := fx
到達第二個。 這個pragma現在拉了重量。 解析D [Integer] [Integer]
,兩個實例都匹配,第一個實例匹配u := [Integer]; a := [Integer]
u := [Integer]; a := [Integer]
,第二個用u := [Integer]; f := []; x := Integer
u := [Integer]; f := []; x := Integer
u := [Integer]; f := []; x := Integer
。 但是,第二個更具體和OVERLAPPING
,因此第一個被作為候選者丟棄並且使用第二個實例。 (旁注:我認為第一個實例應該是OVERLAPPABLE
,第二個實例應該沒有pragma。這樣,所有未來的實例都隱式地重疊了catch-all實例,而不是必須對每個實例進行注釋。)
通過該技巧,選擇以正確的優先級完成,然后強制兩個參數之間的相等。 顯然,這種組合可以達到你想要的效果。
可視化正在發生的事情的一種方法是維恩圖。 從第一次嘗試開始, instance D aa
和instance D u (fx)
形成兩組,每組可以匹配的類型對。 這些集合確實重疊,但是有許多類型只有D aa
匹配,而許多對只有D u (fx)
匹配。 兩者都不能說是更具體,所以OVERLAPPING
pragma失敗了。 在第二次嘗試, D ua
實際上涵蓋對類型的整個宇宙 ,和D u (fx)
是一個子集:它(讀內側)。 現在, OVERLAPPING
pragma工作正常。 以這種方式思考也向我們展示了另一種方法,通過創建一個新的集合來完成第一次嘗試的交集。
instance D a a
instance D u (f x)
instance {-# OVERLAPPING #-} (f x) (f x)
但是我會選擇帶有兩個實例的實例,除非你出於某種原因確實需要使用這個實例。
但請注意,重疊實例被認為有點脆弱。 正如您所注意到的,了解選擇哪個實例以及原因通常很棘手。 人們需要考慮范圍內的所有實例,它們的優先級,並且基本上在一個人的腦海中運行一個非平凡的選擇算法來理解正在發生的事情。 當跨多個模塊(包括孤兒)定義實例時,事情變得更加復雜,因為選擇規則可能根據本地導入而不同。 這甚至可能導致不一致。 最好盡可能避免使用它們。
另見GHC手冊 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.