[英]Monadic type checker in Haskell
我正在從BNFC開始在Haskell中編寫解析器和類型檢查器。 類型檢查器的主要功能實現如下:
typecheck :: Program -> Err ()
typecheck (PDefs ds) = do
env <- foldM (\env (DFun typ id args _ _) ->
updateFun env id (argTypes args,typ) ) (emptyEnv) (ds)
mapM_ (checkDef env) ds
where argTypes = map (\(ADecl _ typ _) -> typ)
其中PDefs
, DFun
和ADecl
是語言抽象語法中定義的代數數據類型的構造函數, checkDef
和updateFun
是函數。 Program
是語法的“起點”。 使用的monad是monad Err
:
data Err a = Ok a | Bad String
deriving (Read, Show, Eq, Ord)
instance Monad Err where
return = Ok
fail = Bad
Ok a >>= f = f a
Bad s >>= f = Bad s
typechecker
函數在“main”模塊中調用(在類型檢查之前有詞法和sintax分析):
check :: String -> IO ()
check s = do
case pProgram (myLexer s) of
Bad err -> do
putStrLn "SYNTAX ERROR"
putStrLn err
exitFailure
Ok tree -> do
case typecheck tree of
Bad err -> do
putStrLn "TYPE ERROR"
putStrLn err
exitFailure
Ok _ -> do
putStrLn "\nParsing e checking ok!"
showTree tree
( tree
是解析器構建的抽象語法樹)
如果作為輸入傳遞的程序中存在類型錯誤,則類型檢查器會返回錯誤,指出錯誤並且不會繼續。 有沒有辦法允許類型檢查器在單次執行中列出輸入中的所有錯誤?
正如@ mb14的評論中所述,通常的方法包括做兩件事:
Writer
monad輕松完成。 在這個簡單的方案中,類型檢查總是返回一個類型化的樹。 如果錯誤消息的日志為空,則類型檢查已成功,並且鍵入的樹有效。 否則,類型檢查已失敗並出現給定的錯誤集,並且可以丟棄鍵入的樹。 在更復雜的方案中,您可以區分日志中的警告和錯誤,並且如果類型檢查包含零個或多個警告但沒有錯誤,則認為類型檢查已成功。
我已經在下面提供了一個完整的技術示例,用於非常簡化的語法。 它只返回頂級類型而不是類型化樹,但這只是為了保持代碼簡單 - 返回類型檢查樹並不困難。 使其適應你的語法的難點在於確定在發生錯誤時如何提前(即,提供什么樣的有效類型),並且它將高度依賴於程序的細節。 一些通用技術如下所示:
Len
),則始終假定該節點的類型,即使該節點未進行類型檢查。 Plus
,或BNF交替),那么當檢測到類型不兼容時,請通過其第一個參數的類型確定節點的類型。 無論如何,這是完整的例子:
import Control.Monad.Writer
-- grammar annotated with node ids ("line numbers")
type ID = String
data Exp = Num ID Double -- numeric literal
| Str ID String -- string literal
| Len ID Exp -- length of a string expression
| Plus ID Exp Exp -- sum of two numeric expressions
-- or concat of two strings
-- expression types
data Type = NumT | StrT deriving (Show, Eq)
-- Expressions:
-- exp1 = 1 + len ("abc" + "def")
-- exp2 = "abc" + len (3 + "def")
-- annotated with node ids
exp1, exp2 :: Exp
exp1 = Plus "T1" (Num "T2" 1)
(Len "T3" (Plus "T4" (Str "T5" "abc")
(Str "T6" "def")))
exp2 = Plus "T1" (Str "T2" "abc")
(Len "T3" (Plus "T4" (Num "T5" 3)
(Str "T6" "def")))
-- type check an expression
data Error = Error ID String deriving (Show)
type TC = Writer [Error]
typeCheck :: Exp -> TC Type
typeCheck (Num _ _) = return NumT
typeCheck (Str _ _) = return StrT
typeCheck (Len i e) = do
t <- typeCheck e
when (t /= StrT) $
tell [Error i ("Len: applied to bad type " ++ show t)]
return NumT -- whether error or not, assume NumT
typeCheck (Plus i d e) = do
s <- typeCheck d
t <- typeCheck e
when (s /= t) $
tell [Error i ("Plus: incompatible types "
++ show s ++ " and " ++ show t
++ ", assuming " ++ show s ++ " result")]
return s -- in case of error assume type of first arg
compile :: String -> Exp -> IO ()
compile progname e = do
putStrLn $ "Running type check on " ++ progname ++ "..."
let (t, errs) = runWriter (typeCheck e)
case errs of
[] -> putStrLn ("Success! Program has type " ++ show t)
_ -> putStr ("Errors:\n" ++
unlines (map fmt errs) ++ "Type check failed.\n")
where fmt (Error i s) = " in term " ++ i ++ ", " ++ s
main :: IO ()
main = do compile "exp1" exp1
compile "exp2" exp2
它生成輸出:
Running type check on exp1...
Success! Program has type NumT
Running type check on exp2...
Errors:
in term T4, Plus: incompatible types NumT and StrT, assuming NumT result
in term T3, Len: applied to bad type NumT
in term T1, Plus: incompatible types StrT and NumT, assuming StrT result
Type check failed.
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.