无GeneralizedNewtypeDeriving扩展时,如何实现Parser的Monad等类型类实例?
没问题,我来一步步拆解怎么在Haskell里手动实现这些类型类实例——完全不用GeneralizedNewtypeDeriving扩展,核心就是理解每个类型类的契约,直接操作Parser的底层逻辑。
首先我们得先定义Parser的核心结构,以及它依赖的状态和错误类型。这里我们做一个带位置追踪的解析器(方便错误定位):
-- 自定义解析错误:包含错误信息和当前行号、列号 data ParseError = ParseError String (Int, Int) deriving (Show, Eq) -- 解析器状态:剩余输入字符串、当前行号、当前列号 data ParseState = ParseState { input :: String , lineNum :: Int , colNum :: Int } deriving (Show, Eq) -- Parser新类型:本质是一个函数,接收当前状态,返回要么错误,要么(解析结果,新状态) newtype Parser a = Parser { runParser :: ParseState -> Either ParseError (a, ParseState) }
接下来我们逐个实现所需的类型类,从最基础的Functor开始:
1. Functor实例
Functor的fmap就是把函数应用到解析结果上,不碰状态或错误逻辑:
instance Functor Parser where fmap func (Parser parseFn) = Parser $ \currentState -> case parseFn currentState of Left err -> Left err Right (result, newState) -> Right (func result, newState)
2. Applicative实例
Applicative需要实现pure和<*>:
pure:返回一个不消耗输入、直接返回指定值的解析器,状态完全不变<*>:先运行第一个解析器拿到函数,再运行第二个解析器拿到值,把函数应用到值上,同时传递状态和错误
instance Applicative Parser where pure val = Parser $ \s -> Right (val, s) (Parser getFunc) <*> (Parser getVal) = Parser $ \currentState -> case getFunc currentState of Left err -> Left err Right (func, nextState) -> case getVal nextState of Left err -> Left err Right (val, finalState) -> Right (func val, finalState)
3. Monad实例
Monad的>>=(bind)是Parser的核心逻辑:先运行第一个解析器得到结果,再用这个结果生成新的解析器,最后运行新的解析器,全程传递状态和错误:
instance Monad Parser where return = pure (Parser parseFn) >>= nextParser = Parser $ \currentState -> case parseFn currentState of Left err -> Left err Right (result, nextState) -> runParser (nextParser result) nextState
4. MonadError实例
首先需要导入Control.Monad.Error.Class(如果用的是较新的Haskell版本,可能需要mtl库的Control.Monad.Except)。我们需要实现throwError和catchError:
throwError:生成一个直接抛出指定错误的解析器,不改变当前状态catchError:运行目标解析器,如果它抛出错误,就用提供的错误处理函数生成新的解析器来处理
import Control.Monad.Error.Class (MonadError(..)) instance MonadError ParseError Parser where throwError err = Parser $ \_ -> Left err catchError (Parser parseFn) errorHandler = Parser $ \currentState -> case parseFn currentState of Left err -> runParser (errorHandler err) currentState Right result -> Right result
5. MonadState实例
导入Control.Monad.State.Class,实现get和put:
get:获取当前的解析状态(剩余输入、行号列号)put:设置新的解析状态
import Control.Monad.State.Class (MonadState(..)) instance MonadState ParseState Parser where get = Parser $ \currentState -> Right (currentState, currentState) put newState = Parser $ \_ -> Right ((), newState)
补充:实用的辅助解析函数
为了让这个Parser能用起来,我们可以加几个简单的辅助函数,比如读取单个字符的解析器:
-- 读取任意一个字符,同时更新行号列号(遇到换行符时行号+1,列号重置为1) anyChar :: Parser Char anyChar = Parser $ \s@(ParseState input line col) -> case input of [] -> Left $ ParseError "Unexpected end of input" (line, col) (c:rest) -> let (newLine, newCol) = if c == '\n' then (line + 1, 1) else (line, col + 1) in Right (c, ParseState rest newLine newCol) -- 解析指定的字符,不匹配就抛出错误 char :: Char -> Parser Char char target = do currentState <- get c <- anyChar if c == target then return c else throwError $ ParseError ("Expected '" ++ [target] ++ "', got '" ++ [c] ++ "'") (lineNum currentState, colNum currentState)
这样就完成了所有类型类的手动实现,完全不需要依赖GeneralizedNewtypeDeriving。关键在于每个类型类的实现都是直接操作Parser内部的函数,严格遵循每个类型类的契约——比如Monad的bind要正确传递状态和错误,MonadState的get/put要正确读取和更新状态。
需要注意的是,你需要确保安装了mtl库(可以通过cabal install mtl或者stack install mtl安装),因为MonadError和MonadState都来自这个库。
内容的提问来源于stack exchange,提问作者Mas Bagol

