Haskell Parser不工作

时间:2013-10-14 17:48:23

标签: parsing haskell

我想了解Parsers。因此我创建了自己的解析器。不幸的是它不起作用。为什么?

type Parser a = String -> [(a, String)]

preturn :: a -> Parser a 
preturn t = \inp -> [(t,inp)] 

pfailure :: Parser a 
pfailure = \inp -> []

pitem :: Parser Char
pitem = \inp -> case inp of 
    [] -> []
    (x:xs) -> [(x,xs)]


parse :: Parser a -> Parser a 
--parse :: Parser a -> String -> [(a,String)]

parse p inp = p inp

{-
combine :: Parser a -> Parser b -> Parser (a,b)
combine p1 p2 = \inp -> p2 t output
    where
        p1 inp = ([


-}
-- firstlast :: Parser (Char,Char)
firstlast = do 
    x <- pitem
    z <- pitem
    y <- pitem
    preturn (x,y)

another = do
    x <- pitem
    y <- pitem

Firstlast应该接受一个字符串并返回第一个和第三个字符。不幸的是,它返回奇数值,并且它不接受它的类型(Parser(Char,Char))

例如,

*Main> firstlast "abc"
[(([('a',"bc")],[('a',"bc")]),"abc")]

应该发生的事情是:

*Main> firstlast "abc"
[("ac",[])]

1 个答案:

答案 0 :(得分:7)

请使用编译的代码。您的another功能没有。

有什么问题?

firstlastanother的代码使用了do - 符号。你在这里使用pitem的方式,看起来好像你期望Parser成为一个monad。但事实并非如此,至少不像你期望的那样。

预定义的monad实例使GHC认为Parser是monad,即

instance Monad ((->) r) where
  return = const
  f >>= k = \ r -> k (f r) r

这个实例说的是,对于任何类型r,函数类型r -> ...都可以被认为是monad,即通过在任何地方分配参数。因此,返回此monad中的某些内容相当于生成忽略类型r的参数的值,并且绑定值意味着您将r并将其传递给左右计算。

这不是您想要的解析器。输入字符串将分发给所有计算。因此每个pitem将对原始输入字符串进行操作。此外,正如

pitem :: String -> [(Char, String)]

您的monadic计算结果将为[(Char, String)]类型,因此xy都属于此类型。这就是你得到结果的原因

[(([('a',"bc")],[('a',"bc")]),"abc")]

您在同一输入字符串上调用pitem三次。你把两个结果放在一对中,你就是preturn整个事情。

如何解决?

您需要为Parser类型定义自己的monad实例。您无法直接执行此操作,因为Parser是类型同义词,并且无法部分应用类型同义词, 所以你不能写

instance Monad Parser where
  ...

相反,您必须将Parser包装在新的数据类型或newtype中:

newtype Parser a = Parser { parse :: String -> [(a, String)] }

这为您提供了一个构造函数Parser和一个函数parse,用于在展开和包装的解析器类型之间进行转换:

Parser :: String -> [(a, String)] -> Parser a
parse  :: Parser a -> String -> [(a, String)]

这意味着您必须调整其他功能。例如,preturn变为

preturn :: a -> Parser a 
preturn t = Parser (\inp -> [(t,inp)])

同样更改pfailurepitem。然后,您必须定义Monad实例:

instance Monad Parser where
  return = preturn
  (>>=)  = ... -- to be completed by you

上面的代码中不包含函数(>>=)。您将希望实现将输入传递给第一个解析器的行为,并且对于每个结果,结果和剩余的输入将传递给(>>=)的第二个参数。完成此操作后,对parse firstlast "abc"的调用将产生以下结果:

[(('a','c'),"")]

这不是你想要的问题,但我相信这是你真正想要的。