簡體   English   中英

Haskell中的Monadic類型檢查器

[英]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)

其中PDefsDFunADecl是語言抽象語法中定義的代數數據類型的構造函數, checkDefupdateFun是函數。 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.

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