You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无GeneralizedNewtypeDeriving扩展时,如何实现Parser的Monad等类型类实例?

手动实现带状态与错误处理的Monadic Parser(无需GeneralizedNewtypeDeriving)

没问题,我来一步步拆解怎么在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:34:57