簡體   English   中英

缺少類型推斷導致編譯失敗,沒有實例模糊

[英]Lack of type inference results in failed compilation, no instance ambiguities

我很困惑為什么這個代碼用類型提示編譯,但沒有編譯。 不應該有任何實例歧義(有一個實例)。

class Monad m => FcnDef β m | β -> m where
    def :: String -> β -- takes a name

instance Monad m => FcnDef (m α -> m α) m where
    def s body = body

dummyTest :: forall m. Monad m => m ()
dummyTest = def "dummy" ((return ()) :: m ())

另一方面,如果省略了:: m ()或所有類型聲明,編譯將失敗並顯示此錯誤,

No instance for (FcnDef (m0 () -> t0) m0)
  arising from a use of `def'

為了澄清,代碼試圖為def創建一個多變量類型,因此可以編寫例如

def "dummy2" "input" $ \in -> return ()

編輯

這個問題比沒有單態限制問題更有趣。 如果添加了這樣的代碼,則將實例解析為具體類型,即

dummyTest = def "dummy" (return ())
g :: IO ()
g = dummyTest

編譯同樣失敗。

外部類型簽名的需要是由單態限制引起的。

這個的贈品是你定義的左側。

dummyTest = ...

由於此定義沒有任何參數,編譯器將嘗試使定義為單態。 添加類型簽名會覆蓋此行為。

但是,正如您所指出的,這還不夠。 由於某種原因,編譯器無法推斷內部表達式的類型。 為什么? 我們來看看。 是時候玩類型推理引擎了!

讓我們從外部類型開始,嘗試計算出內部表達式的類型。

dummyTest :: forall m. Monad m => m ()
dummyTest = def "dummy" (return ())

def的類型是FcnDef β m => String -> β ,但是這里我們將def應用於兩個參數。 這告訴我們β必須是函數類型。 我們稱之為x -> y

然后我們可以很容易地推斷y必須等於m () ,以便滿足外部類型。 此外,參數return ()的類型是Monad m1 => m1 () ,因此我們可以推斷出我們要查找的def類型必須具有類型FcnDef (m1 () -> m ()) m0 => def :: String -> m1 () -> m ()

接下來,我們將繼續查找要使用的實例。 唯一可用的實例不夠通用 ,因為它要求m1m相同。 所以我們大聲抱怨這樣的消息:

Could not deduce (FcnDef (m1 () -> m ()) m0)
  arising from a use of `def'
from the context (Monad m)
  bound by the type signature for dummyTest :: Monad m => m ()
  at FcnDef.hs:10:1-51
Possible fix:
  add (FcnDef (m1 () -> m ()) m0) to the context of
    the type signature for dummyTest :: Monad m => m ()
  or add an instance declaration for (FcnDef (m1 () -> m ()) m0)
In the expression: def "dummy" ((return ()))
In an equation for `dummyTest':
    dummyTest = def "dummy" ((return ()))

這里要注意的關鍵是,當我們試圖推斷出類型時,一個特定實例碰巧躺在身邊的事實並沒有影響我們的選擇。

因此,我們不得不使用范圍類型變量手動指定此約束,或者我們可以在類型類中對其進行編碼。

class Monad m => FcnDef β m | β -> m where
    def :: String -> β -> β

instance Monad m => FcnDef (m α) m where
    def s body = body

-- dummyTest :: forall m. Monad m => m ()
dummyTest = def "dummy" (return ())

現在類型推理引擎可以很容易地確定return的monad必須與結果中的monad相同,並且使用NoMonomorphismRestriction我們也可以刪除外部類型簽名。

當然,我並不是100%肯定你在這里想要完成的事情,所以你必須在你想要做的事情的背景下判斷這是否有意義。

正如@pigworker在評論中指出:

對於未來可能的實例,實例推斷是防御性的。 看起來你的實例頭有兩次相同的var。 除非其他東西強迫這些類型相同,否則它不會觸發。

這確實是一個基本問題,雖然@ hammar添加額外參數的方法可能是最普遍有用的解決方案,但在這樣的情況下,我們可以進行觀察,然后將實例選擇機制的弱點扭曲到我們的利益。 首先,請注意我們不能寫這樣的兩個實例:

instance Monad m => FcnDef (m α -> m α) m where -- etc...
instance Monad m => FcnDef (m1 α -> m2 b) m3 where -- etc...

為什么不? 有點不正常,因為它們重疊太多。 第二種情況下的不同類型變量當然可以用相同的類型合理地實例化,從而匹配第一個實例; OverlappingInstances擴展在這里並沒有真正的幫助。

鑒於第一個實例是我們在這種情況下想要的 ,並且重疊意味着我們永遠不會添加第二個實例,我們可以在GHC上快速推出。 我們將從實際編寫第二個通用實例開始,但之后我們會將某些內容放入上下文(之后僅進行檢查!)中,強制統一類型。 如果你啟用了TypeFamilies你可以在這里使用~效果很好,並寫下:

instance (m1 ~ m2, m2 ~ m3, a ~ b, Monad m) => FcnDef (m1 α -> m2 b) m3 where -- etc...

這里會發生的是GHC將在這里選擇通用實例而不管類型,然后嘗試統一它們以滿足約束。 如果它們無法統一,則會出現預期和期望的類型檢查錯誤。 如果他們匹配,一切都很好。 但最重要的部分是,不像你的代碼,如果他們統一,但仍然模糊,這會導致他們要統一。

在大多數情況下,實例選擇忽略了上下文是一個真正的痛苦,但它在這種情況下實際上非常有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM