Haskell懒惰的问题或为什么这个monad没有按预期工作

时间:2011-01-09 04:29:15

标签: haskell monads

这里有一段很大的代码,它最终变得庞大,因为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)"

2 个答案:

答案 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的第二个参数应该履行什么角色,因为目前主要包含“???”,这看起来并不特别有用。