问题
我正在为自我教育推出一个非确定性解析器,它看起来像:
newtype Parser a b = P { runP :: [a] -> [(b, [a])] }
我希望能够放入一个可能形式的子例程:[a] -> [b]
,它接受一个字符缓冲区并将其发送到结果列表。这里的技巧是子程序本身是一个有状态计算,并在每次成功调用时转换为状态(将其视为有限状态机)。具体做法是:
[]
,则解析器会将另外一个字符串插入缓冲区并将其输出到子例程,该子例程再次运行。 [b]
,则首先刷新缓冲区,然后解析器将另外一个字符串插入缓冲区,使其生成子例程。 [b]
存储在某个地方达到转义条件后,子程序将结果bs
返回给解析器,并将其与剩余的as
流组合,如下所示:
rs = fmap(flip(,)as)bs :: [(b,[a])]
因此满足runP
功能可能具有此签名:withf :: ([a] -> [b]) -> Parser a b
重要的是withf g
必须是解析器,因此我可以使用<*>
构建更大的解析器。请注意函数签名建议g
是一个纯函数,所以它不太可能是正确的。
尝试过的解决方案
我尝试使用各种协同程序包实现这一点,但对我来说,将lift
解析器转换为协同计算上下文更有意义,将其与一个也转换为上下文的转换器组合,这意味着它&# 39; s不再是解析器。
我还尝试将withf
实现为可以访问Parser值构造函数的原始函数。基本上将步骤1..4转换为代码。我在这里遇到的最大问题是谁负责哪些信息:
withf
我还尝试了各种自制的coroutine实现,直接进入解析器(所以不使用上面定义的Parser
类型),但收效甚微。
任何能指出我正确方向的人都非常感谢。
答案 0 :(得分:3)
首先,让我们在解析器中使用MonadPlus
而不是[]
。它会使它更通用并稍微澄清一下代码(我们不会有这么多嵌套的[]
):
newtype Parser a m b = P { runP :: [a] -> m (b, [a]) }
我建议你改变子程序的签名。你需要的是:
这可以通过此类型签名轻松完成:
newtype Sub a b = Sub { runSub :: Either (a -> Sub a b) [b] }
子程序产生结果,或者请求新输入并产生新的子程序。这样,您可以通过将其传递到返回的子例程来保持您需要的任何状态。转换功能将如下所示:
withf :: (MonadPlus m) => Sub a b -> Parser a m b
withf start = P $ f (runSub start)
where
f (Right bs) xs = msum [ return (b, xs) | b <- bs ]
f (Left r) [] = mzero -- No more input, can't proceed.
f (Left r) (x:xs) = f (runSub (r x)) xs
更新:我们可以采取的另一种方法是认识到解析器实际上是StateT
转换器,其状态为[a]
:
type Parser a m b = StateT [a] m b
runP :: (Monad m) => Parser a m b -> [a] -> m (b, [a])
runP = runStateT
确实,runP
正是runStateT
!
这样,我们免费为Monad
获取Parser
个实例。现在我们可以将任务分成更小的块。首先,我们创建一个消耗一个输入的解析器,或者失败:
receive :: (MonadPlus m) => Parser a m a
receive = get >>= f
where
f [] = mzero -- No more input, can't proceed.
f (x:xs) = put xs >> return x
然后用它来描述withf
:
withf :: (MonadPlus m) => Sub a b -> Parser a m b
withf start = f (runSub start)
where
f (Right bs) = msum (map return bs)
f (Left r) = receive >>= f . runSub . r
请注意,如果m
是MonadPlus
,那么StateT s m
也是MonadPlus
,因此我们可以直接使用mzero
和msum
Parser
。
答案 1 :(得分:2)
首先,让我们定义一个新的数据类型来表示解析的可能结果。
data Step r = Done | Fail | Succ r
解析器可以使用Done
结束,使用Fail
指示失败的解析,也可以使用r
成功返回已解析的值Succ r
。
我们会将Step
数据类型设为Monoid
类型类的实例
instance Monoid (Step r) where
mempty = Done
Done `mappend` _ = Done
Fail `mappend` x = x
Succ r `mappend` _ = Succ r
如果我们的解析器是Done
,我们应该立即终止。 Fail
表示我们应该检查下一个Step
的结果,以获得可能的成功。当然,Succ r
意味着我们已经成功解析了一个值。
现在让我们为Parser
定义一个类型同义词。它需要能够累积解析结果(Writer
)并维护一个纯状态,表示尚未消耗的输入(State
)。
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad.State
import Control.Monad.Writer
import Data.List
import Data.Foldable
type Parser w s = WriterT w (State s)
evalParser :: Parser w s r -> s -> w
evalParser = evalState . execWriterT
这是实际的解析器
parser :: (MonadState [s] m, MonadWriter [w] m) => ([s] -> Step [w]) -> m ()
parser sub = do
bufs <- gets inits
-- try our subroutine on increasingly long prefixes until we are done,
-- or there is nothing left to parse, or we successfully parse something
case foldMap sub bufs of
Done -> return ()
Fail -> return ()
Succ r -> do
-- record our parsed result
tell r
-- remove the parsed result from the state
modify (drop $ length r)
-- parse some more
parser sub
和一个简单的测试用例
test :: String
test = evalParser (parse sub) "aabbcdde"
where sub "aabb" = Succ "aabb"
sub "cdd" = Succ "cdd"
sub "e" = Done
sub _ = Fail
-- test == "aabbcdd"