如何判断Parsec解析器是否在Haskell中使用常量堆空间

时间:2017-03-30 13:25:52

标签: parsing haskell heap-memory tail-recursion parsec

recent question中,我询问了以下内容 parsec解析器:

manyLength
  :: forall s u m a. ParsecT s u m a -> ParsecT s u m Int
manyLength p = go 0
  where
    go :: Int -> ParsecT s u m Int
    go !i = (p *> go (i + 1)) <|> pure i

此功能类似于many。但是,它不是返回[a] 返回能够成功运行p的次数。

除了一个问题外,这很有效。它不会在常量堆中运行 空间。

在链接问题中,Li-yao Xia提供了另一种方式 编写使用常量堆空间的manyLength

manyLengthConstantHeap
  :: forall s u m a. ParsecT s u m a -> ParsecT s u m Int
manyLengthConstantHeap p = go 0
  where
    go :: Int -> ParsecT s u m Int
    go !i =
      ((p *> pure True) <|> pure False) >>=
        \success -> if success then go (i+1) else pure i

这是一项重大改进,但我不明白为什么 manyLengthConstantHeap使用常量堆空间,而原始manyLength则不使用。

如果你在(<|>)中内联manyLength,它看起来有点像这样:

manyLengthInline
  :: forall s u m a. Monad m => ParsecT s u m a -> ParsecT s u m Int
manyLengthInline p = go 0
  where
    go :: Int -> ParsecT s u m Int
    go !i =
      ParsecT $ \s cok cerr eok eerr ->
        let meerr :: ParserError -> m b
            meerr err =
              let neok :: Int -> State s u -> ParserError -> m b
                  neok y s' err' = eok y s' (mergeError err err')
                  neerr :: ParserError -> m b
                  neerr err' = eerr $ mergeError err err'
              in unParser (pure i) s cok cerr neok neerr
        in unParser (p *> go (i + 1)) s cok cerr eok meerr

如果你在(>>=)中内联manyLengthConstantHeap,它看起来有点像这样:

manyLengthConstantHeapInline
  :: forall s u m a. Monad m => ParsecT s u m a -> ParsecT s u m Int
manyLengthConstantHeapInline p = go 0
  where
    go :: Int -> ParsecT s u m Int
    go !i =
      ParsecT $ \s cok cerr eok eerr ->
        let mcok :: Bool -> State s u -> ParserError -> m b
            mcok success s' err =
                let peok :: Int -> State s u -> ParserError -> m b
                    peok int s'' err' = cok int s'' (mergeError err err')
                    peerr :: ParserError -> m b
                    peerr err' = cerr (mergeError err err')
                in unParser
                    (if success then go (i + 1) else pure i)
                    s'
                    cok
                    cerr
                    peok
                    peerr
            meok :: Bool -> State s u -> ParserError -> m b
            meok success s' err =
                let peok :: Int -> State s u -> ParserError -> m b
                    peok int s'' err' = eok int s'' (mergeError err err')
                    peerr :: ParserError -> m b
                    peerr err' = eerr (mergeError err err')
                in unParser
                    (if success then go (i + 1) else pure i)
                    s'
                    cok
                    pcerr
                    peok
                    peerr
        in unParser ((p *> pure True) <|> pure False) s mcok cerr meok eerr

这是完整性的ParsecT构造函数:

newtype ParsecT s u m a = ParsecT
  { unParser
      :: forall b .
         State s u
      -> (a -> State s u -> ParseError -> m b) -- consumed ok
      -> (ParseError -> m b)                   -- consumed err
      -> (a -> State s u -> ParseError -> m b) -- empty ok
      -> (ParseError -> m b)                   -- empty err
      -> m b
  }

为什么manyLengthConstantHeap以恒定的堆空间运行,而 manyLength不是吗?它看起来不像go的递归调用 manyLengthConstantHeapmanyLength的尾部调用位置。

将来编写parsec解析器时,如何知道空间 给定解析器的要求?夏丽瑶是怎么知道的 manyLengthConstantHeap可以吗?

我觉得我没有信心预测哪些解析器会使用 大输入上的大量内存。

是否有一种简单的方法可以确定给定的函数是否存在 Haskell中的尾递归而不运行它?或者更好,没有编译 它?

0 个答案:

没有答案