如何在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))
代码说明
parseLastDigit:用lookAhead预判最后一个数字序列的位置,跳过前面所有内容后解析目标数字,直接实现“找最后一个数字”的逻辑,无需递归。optional parseLastDigit:替代原有的ranParserRight逻辑,解析成功返回Just Int,无数字时返回Nothing。getnotdigits:改用many_替代many,因为我们不需要保留跳过的非数字字符,优化性能。
如果一定要保留原代码中parseright的递归逻辑,也可以通过获取剩余输入并应用parseMaybe来包装成Parser:
simpleParserRight :: Parser (Maybe Int) simpleParserRight = do rest <- takeRest return $ parseMaybe parseright rest
但这种方式不如直接用组合子解析高效,且仍需注意parseright的递归终止条件(避免无限递归)。
内容的提问来源于stack exchange,提问作者Ashok Kimmel
相关产品推荐
相关产品推荐

