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

如何在Haskell(Megaparsec)中将函数转换为解析器?

解决Megaparsec解析字符串首尾数字乘积的重构问题

现有代码

type Parser = Parsec Void String
parsefirst :: Parser Int 
parsefirst = do 
    getnotdigits 
    x <- some digitChar
    y <- some finishstr 
    let z = ranParserRight y
    return $ read x * getrightnum x z
parseright :: Parser Int 
parseright = do 
    getnotdigits 
    x <- some digitChar
    y <- finishstr 
    let z = ranParserRight y
    return $ getrightnum x z
ranParserRight = parseMaybe parseright
getrightnum = fromMaybe . read 
finishstr = many (satisfy (const True))
getnotdigits = many (satisfy (not . isDigit))

期望重构后的代码

type Parser = Parsec Void String
parsefirst :: Parser Int 
parsefirst = do 
    getnotdigits 
    x <- some digitChar
    z <- simpleParserRight
    return $ read x * getrightnum x z
parseright :: Parser Int 
parseright = do 
    getnotdigits 
    x <- some digitChar
    z <- simpleParserRight 
    return $ getrightnum x z
simpleParserRight = toParser (parseMaybe parseright)
getrightnum = fromMaybe . read 
getnotdigits = many (satisfy (not . isDigit))

问题核心

你想要的toParser (parseMaybe parseright)无法直接实现,因为parseMaybe parseright的类型是String -> Maybe Int,而Megaparsec的Parser Int是带输入状态的Monad,两者类型不兼容。此外原代码中parseright递归调用自己的逻辑可以简化,无需通过parseMaybe来回转换输入。

可行解决方案

我们可以直接用Megaparsec的组合子实现simpleParserRight的逻辑——解析剩余输入中的最后一个数字序列,没有则返回Nothing,避免类型转换和递归依赖:

import Text.Megaparsec
import Text.Megaparsec.Char
import Control.Applicative (optional)
import Data.Maybe (fromMaybe)

type Parser = Parsec Void String

parsefirst :: Parser Int 
parsefirst = do 
    getnotdigits 
    x <- some digitChar
    -- 尝试解析剩余输入的最后一个数字,无则返回Nothing
    z <- optional parseLastDigit
    return $ read x * getrightnum x z

-- 直接解析输入中的最后一个数字序列
parseLastDigit :: Parser Int 
parseLastDigit = do
    -- 跳过所有内容,直到最后一个数字序列的前置位置
    _ <- manyTill anyChar (lookAhead (getnotdigits *> some digitChar <* eof)) <|> return ()
    getnotdigits
    x <- some digitChar
    return (read x)

-- 兼容原逻辑:若没有找到后续数字,返回当前数字的数值
getrightnum :: String -> Maybe Int -> Int
getrightnum = fromMaybe . read 

-- 优化为many_,无需保留跳过的非数字字符结果
getnotdigits :: Parser ()
getnotdigits = many_ (satisfy (not . isDigit))

代码说明

  1. parseLastDigit:用lookAhead预判最后一个数字序列的位置,跳过前面所有内容后解析目标数字,直接实现“找最后一个数字”的逻辑,无需递归。
  2. optional parseLastDigit:替代原有的ranParserRight逻辑,解析成功返回Just Int,无数字时返回Nothing。
  3. getnotdigits:改用many_替代many,因为我们不需要保留跳过的非数字字符,优化性能。

如果一定要保留原代码中parseright的递归逻辑,也可以通过获取剩余输入并应用parseMaybe来包装成Parser:

simpleParserRight :: Parser (Maybe Int)
simpleParserRight = do
    rest <- takeRest
    return $ parseMaybe parseright rest

但这种方式不如直接用组合子解析高效,且仍需注意parseright的递归终止条件(避免无限递归)。

内容的提问来源于stack exchange,提问作者Ashok Kimmel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 13:10:58