这里有一段很大的代码,它最终变得庞大,因为monadic的东西,但任务很简单:解析字符串到数据结构:
“你好(一些,args)” - > [(“fid”,“hello”),(“sym”,“(”),(“args”,“some,args”),(“sym”,“)”)]
但我编写的代码产生以下内容:
“你好(一些,args)” - > [( “FID”, “”),( “符号”, “(”),( “ARGS”, “”),( “符号”, “)”)]
因为你可以看到'args'和'fid'值在路上某处丢失,我怀疑编译器决定不为某些神秘的原因计算它们。
我猜代码完全不好,我也标有“?”的部分,对我来说似乎没用,但是编译器强迫我把它们留在原地:)
以下是代码:
type PStream = String
type PToken a = (String, a)
data Pstate a = Pstate (String -> ([PToken String], PStream)) a
instance Monad Pstate where
return x = Pstate (\_ -> ([("start", "")], "?")) x -- not used ?
(Pstate bindparser v) >>= f = Pstate newparser fv
where
Pstate fparser fv = f v
(btok, brest) = bindparser "this string also not used"
(tok, rest) = fparser brest
newparser _ = (btok ++ tok, rest)
-- parsers
parseFid :: Pstate String
parseFid = Pstate parser "???"
where parser r = let (fid, rest) = span (/= '(') r in ([("fid", fid)],rest)
parseSym :: Char -> Pstate String
parseSym c = Pstate parser "???"
where parser r = let rest = parseOne c r in ([("sym", [c])],rest)
parseOne s (h:t) = if h == s then t else error $ "symbol not match:" ++ [h] ++ " /= " ++ [s]
parseOne s [] = []
parseArgs :: Pstate String
parseArgs = Pstate parser "???"
where parser r = let (args,rest) = span (/=')') r in ([("args", args)],rest)
-- util
load :: String -> Pstate String
load s = Pstate (\ls -> ([("load", "")],ls)) "???"
runP :: Pstate String -> ([PToken String], PStream)
runP (Pstate fparser fv) = fparser "???"
-- combined parser
parseFunction :: String -> Pstate String
parseFunction s = do
load s --- should be 'return' here ?
parseFid
parseSym '('
parseArgs
parseSym ')'
main = putStrLn $ show $ runP $ parseFunction "hello(a b c)"
答案 0 :(得分:3)
首先,关于你必须离开那里的"???"
。考虑您对Pstate
:
data Pstate a = Pstate (String -> ([PToken String], PStream)) a
这意味着,您的数据构造函数具有以下类型:
Pstate :: (String -> ([PToken String], PStream)) -> a -> Pstate a
这是monad的默认构造。如果你定义了monadic组合器,那么在不需要这些组合器的情况下实际上并不罕见,因此惯例是在这种情况下将它留给()
。
但实际上我认为你的代码非常奇怪,似乎你没有抓住有状态monad的观点。让我解释一下:
通常,有状态计算具有以下类型:
data MyState a = MyState (TypeOfState -> (a, TypeOfState))
这意味着,你的monadic动作实际上是某种计算,它可以做某些事情(可能与你的状态有关),而不是返回结果和新状态。国家被包裹在monad中,所以你不必考虑它。
在您的代码中,您使用相同的模式,但有些不同。您似乎将计算结果修改为[PToken String]
。让我稍微修改一下你的定义:
data Pstate a = Pstate (PStream -> (a, PStream))
现在,通过应用组合器得到计算的返回值,如下所示:
instance Monad Pstate where
-- return should just wrap the computation up, so no changes
return x = Pstate (\p -> (x,p))
parser1 >>= parser2 = Pstate $ \input -> let
Pstate parser1' = parser1
Pstate parser2' = parser2
(output, rest) = parser1' input
result = parser2' output rest
in result
现在,您可以查看解析器的类型签名,它们应该是这样的:parseFid :: Pstate [PToken PStream]
。这意味着,您的解析器会消耗一些输入并将解析后的内容返回为[PToken PStream]
,并将新输入设置为剩下的内容。考虑parseFid
关于它的外观的定义:
parseFid :: Pstate [PToken PStream]
parseFid = Pstate $ \r -> let (fid, rest) = span (/= '(') r in ([("fid", fid)],rest)
其余部分留给读者练习。我建议您使用State
中的Control.Monad.State.Strict
monad重新构建解析器。你会看到,上面的monad基本相同。
实际上,大多数时候更容易依赖现有的和众所周知的工具,而不是简化自己的解析器。这是一个解析器,用于您需要使用Parsec
创建的内容,这是一个用于解析的最先进的库:
import Text.Parsec
parseFunction = do name <- parseName
obrace <- parseOpeningBrace
args <- parseArguments
cbrace <- parseClosingBrace
return [name,obrace,args,cbrace]
parseName = many (noneOf "(") >>= \name -> return ("fid",name)
parseOpeningBrace = char '(' >> return ("sym","(")
parseArguments = many (noneOf ")") >>= \name -> return ("args",name)
parseClosingBrace = char ')' >> return ("sym",")")
main = case parse parseFunction "" "hello(some, args)" of
Left error -> print error
Right result -> print result
这是输出:
[("fid","hello"),("sym","("),("args","some, args"),("sym",")")]
我实际上建议你考虑更好地表示已解析的函数,这可能会使事情变得更容易。
答案 1 :(得分:2)
如果您运行代码已发布,您可以看到"this string also not used"
字符串实际上已被使用,因为您获得此输出:
([("load",""),("fid","this string also not used"),("sym","("),("args","this string also not used"),("sym",")")],"")
实际上,字符串基本上用作所有解析器的输入。在>>=
的定义中,字符串作为bindparser
的输入。然后,此解析器将其作为输入并从中创建令牌。例如,parseFid
会生成令牌("fid","this string also not used")
。
newparser
中构造的>>=
会忽略稍后可能收到的任何输入,它只会返回解析"this string also not used"
的结果。类似地,使用return
创建的解析器会忽略它应返回的值。
使用bind创建的解析器不应忽略/覆盖它们的输入以使解析正常工作。
此外,你应该决定Pstate的第二个参数应该履行什么角色,因为目前主要包含“???”,这看起来并不特别有用。