簡體   English   中英

為什么需要減少上下文?

[英]Why is context reduction necessary?

我剛讀過這篇論文 (“類型類:Peyton Jones&Jones對設計空間的探索”),它解釋了Haskell早期類型類系統的一些挑戰,以及如何改進它。

他們提出的許多問題都與上下文減少有關,這是通過遵循“反向蘊涵”關系來減少實例和函數聲明的約束集的一種方法。

例如,如果你有某個instance (Ord a, Ord b) => Ord (a, b) ...那么在上下文中, Ord (a, b)會減少到{Ord a, Ord b} (減少並不總是縮小約束的數量)。

我不明白為什么這種減少是必要的。

好吧,我收集它用於執行某種形式的類型檢查。 當你有一組簡化的約束時,你可以檢查是否存在一些可以滿足它們的實例,否則就是一個錯誤。 我不太確定它的附加價值是什么,因為你會注意到使用網站上的問題,但沒關系。

但即使您必須進行檢查,為什么要在推斷類型中使用縮減結果? 該文指出它導致了不直觀的推斷類型。

這篇論文非常古老(1997),但就我所知,背景減少仍然是一個持續關注的問題。 Haskell 2010規范確實提到了我在上面解釋的推理行為( 鏈接 )。

那么,為什么這樣呢?

我不知道這是否是The Reason,但是它可能被認為是一個原因:在早期的Haskell中,類型簽名只允許具有“簡單”約束,即應用於類型變量的類型類名。 因此,例如,所有這些都沒問題:

Ord a => a -> a -> Bool
Eq a => a -> a -> Bool
Graph gr => gr n e -> [n]

但這些都不是:

Ord (Tree a) => Tree a -> Tree a -> Bool
Eq (a -> b) => (a -> b) -> (a -> b) -> Bool
Graph Gr => Gr n e -> [n]

我認為當時有一種感覺 - 現在仍然是 - 允許編譯器推斷出一個無法手動編寫的類型會有點不幸。 上下文縮減是一種將上述簽名轉換為可以手寫的簽名信息錯誤的方法。 例如,因為一個人可能合理地擁有

instance Ord a => Ord (Tree a)

在范圍上,我們可以將非法簽名Ord (Tree a) => ...轉換為合法簽名Ord a => ... 另一方面,如果我們在范圍內沒有任何Eq實例,則會報告有關在其上下文中推斷為需要Eq (a -> b)的類型的錯誤。

這還有其他一些好處:

  1. 直覺上令人愉悅。 許多上下文縮減規則不會改變類型是否合法,但確實反映了人類在編寫類型時會做的事情。 我在這里考慮重復數據刪除和包含規則,讓你轉向,例如(Eq a, Eq a, Ord a)Ord a - 一個肯定想要做的可轉換性轉換。
  2. 這經常會發現愚蠢的錯誤; 而不是推斷像Eq (Integer -> Integer) => Bool這樣的類型,不能以一種守法的方式滿足,可以報告錯誤, Perhaps you did not apply a function to enough arguments? 更友好!
  3. 編譯器的工作就是找出出錯的地方。 而不是推斷像Eq (Tree (Grizwump a, [Flagle (Gr ne) (Gr n' e') c]))這樣復雜的上下文並且抱怨上下文不可滿足,而是強制將其減少為成分限制; 相反,它會抱怨我們無法從現有的上下文中確定Eq (Grizwump a) - 這是一個更加精確和可操作的錯誤。

我認為這在傳遞實現的字典中確實是可取的。 在這樣的實現中,“字典”,即元組或函數記錄被作為所應用函數的類型中的每個類型類約束的隱式參數傳遞。

現在,問題是這些詞典的創建時間和方式。 觀察對於像Int這樣的簡單類型,所有類型類Int所有字典都是一個常量的實例。 在參數化類型(如列表, Maybe或元組)的情況下不是這樣。 很明顯,要顯示元組,例如,需要知道實際元組元素的Show實例。 因此,這樣的多態字典不能是常數。

似乎指導字典傳遞的原則是這樣的:只傳遞在所應用函數的類型中作為類型變量出現的類型的字典。 或者,換句話說:沒有復制冗余信息。

考慮這個功能:

 f :: (Show a, Show b) => (a,b) -> Int
 f ab = length (show ab)

可顯示組件元組的信息也是可顯示的,因此當我們已經知道(Show a, Show b)時,不需要出現像Show (a,b)這樣的約束。

但是,另一種實現方式是可能的,其中調用者將負責創建和傳遞字典。 這可以在沒有上下文減少的情況下工作,這樣f的類型看起來像:

f :: Show (a,b) => (a,b) -> Int

但這意味着創建元組字典的代碼必須在每個調用站點上重復。 並且很容易得出必要約束數量實際增加的示例,例如:

g :: (Show (a,a), Show(b,b), Show (a,b), Show (b, a)) => a -> b -> Int
g a b = maximum (map length [show (a,a), show (a,b), show (b,a), show(b,b)])

使用顯式傳遞的實際記錄實現類型類/實例系統是有益的。 例如:

data Show' a = Show' { show' :: a -> String }
showInt :: Show' Int
showInt = Show' { show' = intshow } where
      intshow :: Int -> String
      intshow = show

一旦這樣做,您可能很容易認識到“減少上下文”的必要性。

暫無
暫無

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

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