[英]Haskell: What does it mean for a type signature to be total?
我有以下代碼,概述了布爾和算術表達式的語言:
data Exp a where
Plus :: Exp Int -> Exp Int -> Exp Int
Const :: (Show a) => a -> Exp a
Not :: Exp Bool -> Exp Bool
And :: Exp Bool -> Exp Bool -> Exp Bool
Greater :: Exp Int -> Exp Int -> Exp Bool
以下是僅評估算術表達式的函數的代碼:
evalA (Plus a b) = evalA a + evalA b
evalA (Const a) = a
我試圖弄清楚應該為evalA
賦予哪種類型的簽名,以使其完整。 但是,我不知道對一個類型簽名進行總計意味着什么。 任何見解都表示贊賞。
另一個答案說明“合計”是函數的屬性,而不是類型簽名。 然后繼續說,如果您希望函數總計,則必須涵蓋GADT的其他構造函數。 但這還不是全部。
真正的故事是,對於具有高級類型系統(如Haskell)的語言,“總計”是函數和類型簽名之間的關系 。 因此,它確實不是類型簽名的屬性(說“這種類型簽名是總計”是沒有道理的); 但是它也不是函數的屬性(單獨說“這個函數是合計的!” 1 )沒有意義。
現在,讓我們回到您的問題。 你說:
data Exp a where
Plus :: Exp Int -> Exp Int -> Exp Int
Const :: (Show a) => a -> Exp a
Not :: Exp Bool -> Exp Bool
And :: Exp Bool -> Exp Bool -> Exp Bool
Greater :: Exp Int -> Exp Int -> Exp Bool
evalA (Plus a b) = evalA a + evalA b
evalA (Const a) = a
有了我們最新的理解,我們現在可以提出一個新的更好,更精確的問題,即:是否存在evalA
的類型簽名,當與該實現配對時,該配對導致總的配對? 這個更好的問題的答案是肯定的 ,與另一個答案中的說法相反,該說法指出您必須實施更多的evalA
。 特別是如果我們寫
evalA :: Exp Int -> Int
evalA (Plus a b) = evalA a + evalA b
evalA (Const a) = a
則在有限時間內將任何類型正確的evalA
應用於有限輸入都將產生一個非底答案。 (這是功能“合計”的一種明智含義。)
為什么我們可以忽略Not
, And
和Greater
情況? 為什么,因為我們要求輸入的類型為Exp Int
,並且外部構造函數為Not
, And
或Greater
任何類型良好的術語實際上都將具有Exp Bool
類型-因此應用程序的類型將不正確。 因此,這不會因模式匹配錯誤而導致崩潰,您可能會擔心!
1 可能會說“給定類型檢查的任何類型簽名,此功能都是合計的”。 實際上,通常說“此功能是全部功能”作為表示該功能的方便快捷方式。 另一個答案顯示了無論給定哪種(正確)類型簽名,如何使您的函數合計。
類型簽名不能為“總計”或“非總計”。 充其量,使用這樣的術語,有人可以引用聲稱總是返回結果的類型(非終止除外):
foo :: .. -> .. -> Result
畢竟,與將結果包裝在Maybe
或類似的類型相反,表明結果可能不存在:
foo :: .. -> .. -> Maybe Result
這只是術語的延伸,我不會那樣使用。
無論如何,您提到的Exp a
類型是GADT,這是Haskell的一項高級功能。 它允許您定義
evalA :: Exp a -> a
evalA (Plus a b) = evalA a + evalA b
evalA (Const a) = a
-- you should cover the other cases as well here
而不要求您使用Maybe
或類似方法包裝返回類型,這與常規代數類型一樣。
讓我們考慮一個更簡單的示例:僅包含整數和布爾文字的語言。
data Exp where
I :: Int -> Exp
B :: Bool -> Exp
現在,不使用一些難看的技巧就無法定義semExpInt :: Exp -> Int
:
semExpInt :: Exp -> Int
semExpInt (I i) = i -- OK!
semExpInt (B b) = error "not an Int!" -- ugly!
在后一種情況下,我們需要引發運行時錯誤,無法終止或返回任意整數。 本質上,我們在Exp
內發現一個“運行時類型錯誤”,它表示錯誤類型的值( Bool
而不是Int
)。
如果我們嘗試semExpBool :: Exp -> Bool
遇到類似的問題。
我們可以並且應該使用Maybe
報告錯誤:
semExpInt :: Exp -> Maybe Int
semExpInt (I i) = Just i -- OK
semExpInt (B b) = Nothing -- OK, no result here
很好,但是不方便。 我們仍在以某種方式報告“表達式中的運行時錯誤”( Nothing
)。 如果我們可以避免這種情況,那就最好通過輸入一個我們知道正確類型的表達式作為輸入。 使用GADT,我們可以編寫
data Exp t where
I :: Int -> Exp Int
B :: Bool -> Exp Bool
semExpInt :: Exp Int -> Int
semExpInt (I i) = i -- no other cases to handle!
semExpBool :: Exp Bool -> Bool
semExpBool (B b) = b -- no other cases to handle!
或者,甚至更好的是,我們可以將兩個功能合而為一:
semExp :: Exp t -> t
semExp (I i) = i
semExp (B b) = b
在這里,我們宣稱的結果類型正是類型t
其由輸入類型進行Exp t
。 因此,此函數將根據輸入類型返回Int
或Bool
。
將運算符添加到表達式時,這甚至更加方便。 例如,
data Exp where
I :: Int -> Exp
B :: Bool -> Exp
And :: Exp -> Exp -> Exp
允許And (B True) (B False)
,這很好,但也允許And (I 2) (B False)
毫無意義,因為And
只應用於布爾值。 這必須用語義來處理:
semExpBool :: Exp -> Maybe Bool
semExpBool (I i) = Nothing
semExpBool (B b) = Just b
semExpBool (And e1 e2) = case (semExpBool e1, semExpBool e3) of
(Just b1, Just b2) -> Just (b1 && b2)
_ -> Nothing -- some arg was not a bool!
使用GADT,我們可以這樣表達:
data Exp t where
I :: Int -> Exp Int
B :: Bool -> Exp Bool
And :: Exp Bool -> Exp Bool -> Exp Bool
現在, And (I 2) (B False)
被禁止,因為And
需要Exp Bool
參數,而I 2
不是這樣。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.