【问题标题】:Applicative parser stuck in infinite loop应用程序解析器陷入无限循环
【发布时间】:2019-10-30 12:24:52
【问题描述】:

我正在尝试实现自己的 Applicative 解析器,这是我使用的代码:

{-# LANGUAGE ApplicativeDo, LambdaCase #-}

module Parser where

-- Implementation of an Applicative Parser

import Data.Char
import Control.Applicative (some, many, empty, (<*>), (<$>), (<|>), Alternative)

data Parser a = Parser { runParser :: String -> [(a, String)] }

instance Functor Parser where
  -- fmap :: (a -> b) -> (Parser a -> Parser b)
  fmap f (Parser p) = Parser (\s -> [(f a, s') | (a,s') <- p s])

instance Applicative Parser where
  -- pure :: a -> Parser a
  -- <*> :: Parser (a -> b) -> Parser a -> Parser b
  pure x = Parser $ \s -> [(x, s)]
  (Parser pf) <*> (Parser p) = Parser $ \s -> 
               [(f a, s'') | (f, s') <- pf s, (a, s'') <- p s']

instance Alternative Parser where
  -- empty :: Parser a
  -- <|> :: Parser a -> Parser a -> Parser a
  empty = Parser $ \_s -> []
  (Parser p1) <|> (Parser p2) = Parser $ \s ->
      case p1 s of [] -> p2 s
                   xs -> xs

char :: Char -> Parser Char
char c = Parser $ \case (c':cs) | c == c' -> [(c,cs)] ; _ -> []

main = print $ runParser (some $ char 'A') "AAA"

当我运行它时,它会卡住并且永远不会返回。在深入研究问题后,我确定了根本原因是我实现了&lt;|&gt; 方法。如果我使用以下实现,那么一切都会按预期进行:

instance Alternative Parser where
  empty = Parser $ \_s -> []
  p1 <|> p2 = Parser $ \s ->
    case runParser p1 s of [] -> runParser p2 s
                           xs -> xs

在我的理解中,这两个实现是完全等价的。我猜这可能与 Haskell 的惰性评估方案有关。有人可以解释发生了什么吗?

【问题讨论】:

  • 另外,如果您使用的是列表而不是 Maybe,我希望您的 &lt;|&gt; 实现是无条件地调用两个解析器,并将它们的结果与 (++) 结合起来; (Parser p) &lt;|&gt; (Parser q) = Parser $ (\s -&gt; p s ++ q s)
  • @amalloy:你是对的,但无论哪种方式,它都会陷入同一个循环。
  • @ShouYa 这不仅仅是“这两种不同的方法在概念上是相同的”,而是“你已经粘贴了两次完全相同的代码,并在问为什么它只能工作一次”。
  • @amalloy 哎呀我的错。固定:)

标签: parsing haskell infinite-loop lazy-evaluation alternative-functor


【解决方案1】:

事实“明星”:在您的(&lt;*&gt;) 实施中:

Parser p1 <*> Parser p2 = ...

...我们必须进行足够的计算,以知道这两个参数实际上都是 Parser 构造函数对某事物的应用,然后我们才能继续等式的右侧。

事实“管道严格”:在此实现中:

Parser p1 <|> Parser p2 = ...

...我们必须进行足够的计算以知道两个解析器实际上都是 Parser 构造函数的应用程序,然后我们才能继续等号的右侧。

事实“管道懒惰”:在这个实现中:

p1 <|> p2 = Parser $ ...

...我们可以继续等号的右侧而不对p1p2 进行任何计算。

这很重要,因为:

some v = some_v where
    some_v = pure (:) <*> v <*> (some_v <|> pure [])

让我们来看看你的第一个实现,我们知道“管道严格”的事实。我们想知道some_v 是否是Parser 的应用。由于事实“星”,因此我们必须知道pure (:)vsome_v &lt;|&gt; pure [] 是否是Parser 对某事物的应用。要知道最后一个,实际上“管道严格”,我们必须知道some_vpure [] 是否是Parser 的应用程序。哎呀!我们刚刚展示了要知道some_v 是否是Parser 对某事物的应用,我们需要知道some_v 是否是Parser 对某事物的应用——一个无限循环!

另一方面,在您的第二个实现中,要检查 some_v 是否为 Parser _,我们仍然必须检查 pure (:)vsome_v &lt;|&gt; pure [],但感谢“管道懒惰”这一事实,这所有我们需要检查 - 我们可以确信 some_v &lt;|&gt; pure []Parser _ 而无需首先递归检查 some_vpure [] 是。

(接下来,您将了解newtype——当从data 更改为newtype 时,您将再次感到困惑both 实现工作!)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-29
    • 2021-12-31
    • 2020-05-13
    相关资源
    最近更新 更多