簡體   English   中英

Haskell:類型簽名完全是什么意思?

[英]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應用於有限輸入都將產生一個非底答案。 (這是功能“合計”的一種明智含義。)

為什么我們可以忽略NotAndGreater情況? 為什么,因為我們要求輸入的類型為Exp Int ,並且外部構造函數為NotAndGreater任何類型良好的術語實際上都將具有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 因此,此函數將根據輸入類型返回IntBool

將運算符添加到表達式時,這甚至更加方便。 例如,

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.

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