堆栈溢出,其中两个函数在应用解析器中相互调用

我正在上data61的课程:https://github.com/data61/fp-course。在解析器一中,以下实现将导致parse (list1 (character *> valueParser 'v')) "abc"堆栈溢出。

现有代码:

data List t =
  Nil
  | t :. List t
  deriving (Eq,Ord)

-- Right-associative
infixr 5 :.

type Input = Chars

data ParseResult a =
    UnexpectedEof
  | ExpectedEof Input
  | UnexpectedChar Char
  | UnexpectedString Chars
  | Result Input a
  deriving Eq

instance Show a => Show (ParseResult a) where
  show UnexpectedEof =
    "Unexpected end of stream"
  show (ExpectedEof i) =
    stringconcat ["Expected end of stream,but got >",show i,"<"]
  show (UnexpectedChar c) =
    stringconcat ["Unexpected character: ",show [c]]
  show (UnexpectedString s) =
    stringconcat ["Unexpected string: ",show s]
  show (Result i a) =
    stringconcat ["Result >",hlist i,"< ",show a]

instance Functor ParseResult where
  _ <$> UnexpectedEof =
    UnexpectedEof
  _ <$> ExpectedEof i =
    ExpectedEof i
  _ <$> UnexpectedChar c =
    UnexpectedChar c
  _ <$> UnexpectedString s =
    UnexpectedString s
  f <$> Result i a =
    Result i (f a)

-- Function to determine is a parse result is an error.
isErrorResult ::
  ParseResult a
  -> Bool
isErrorResult (Result _ _) =
  False
isErrorResult UnexpectedEof =
  True
isErrorResult (ExpectedEof _) =
  True
isErrorResult (UnexpectedChar _) =
  True
isErrorResult (UnexpectedString _) =
  True

-- | Runs the given function on a successful parse result. Otherwise return the same failing parse result.
onResult ::
  ParseResult a
  -> (Input -> a -> ParseResult b)
  -> ParseResult b
onResult UnexpectedEof _ = 
  UnexpectedEof
onResult (ExpectedEof i) _ = 
  ExpectedEof i
onResult (UnexpectedChar c) _ = 
  UnexpectedChar c
onResult (UnexpectedString s)  _ = 
  UnexpectedString s
onResult (Result i a) k = 
  k i a

data Parser a = P (Input -> ParseResult a)

parse ::
  Parser a
  -> Input
  -> ParseResult a
parse (P p) =
  p

-- | Produces a parser that always fails with @UnexpectedChar@ using the given character.
unexpectedCharParser ::
  Char
  -> Parser a
unexpectedCharParser c =
  P (\_ -> UnexpectedChar c)

--- | Return a parser that always returns the given parse result.
---
--- >>> isErrorResult (parse (constantParser UnexpectedEof) "abc")
--- True
constantParser ::
  ParseResult a
  -> Parser a
constantParser =
  P . const

-- | Return a parser that succeeds with a character off the input or fails with an error if the input is empty.
--
-- >>> parse character "abc"
-- Result >bc< 'a'
--
-- >>> isErrorResult (parse character "")
-- True
character ::
  Parser Char
character = P p
  where p Nil = UnexpectedString Nil
        p (a :. as) = Result as a

-- | Parsers can map.
-- Write a Functor instance for a @Parser@.
--
-- >>> parse (toUpper <$> character) "amz"
-- Result >mz< 'A'
instance Functor Parser where
  (<$>) ::
    (a -> b)
    -> Parser a
    -> Parser b
  f <$> P p = P p'
    where p' input = f <$> p input 

-- | Return a parser that always succeeds with the given value and consumes no input.
--
-- >>> parse (valueParser 3) "abc"
-- Result >abc< 3
valueParser ::
  a
  -> Parser a
valueParser a = P p
  where p input = Result input a

-- | Return a parser that tries the first parser for a successful value.
--
--   * If the first parser succeeds then use this parser.
--
--   * If the first parser fails,try the second parser.
--
-- >>> parse (character ||| valueParser 'v') ""
-- Result >< 'v'
--
-- >>> parse (constantParser UnexpectedEof ||| valueParser 'v') ""
-- Result >< 'v'
--
-- >>> parse (character ||| valueParser 'v') "abc"
-- Result >bc< 'a'
--
-- >>> parse (constantParser UnexpectedEof ||| valueParser 'v') "abc"
-- Result >abc< 'v'
(|||) ::
  Parser a
  -> Parser a
  -> Parser a
P a ||| P b = P c
  where c input
          | isErrorResult resultA = b input
          | otherwise = resultA
            where resultA = a input

infixl 3 |||

我的代码:

instance Monad Parser where
  (=<<) ::
    (a -> Parser b)
    -> Parser a
    -> Parser b
  f =<< P a = P p
    where p input = onResult (a input) (\i r -> parse (f r) i)

instance Applicative Parser where
  (<*>) ::
    Parser (a -> b)
    -> Parser a
    -> Parser b
  P f <*> P a = P b
    where b input = onResult (f input) (\i f' -> f' <$> a i)

list ::
  Parser a
  -> Parser (List a)
list p = list1 p ||| pure Nil

list1 ::
  Parser a
  -> Parser (List a)
list1 p = (:.) <$> p <*> list p

但是,如果我将list更改为不使用list1,或在=<<中使用list1,则效果很好。如果<*>使用=<<,它也可以工作。我觉得这可能与尾递归有关。

更新:

如果我在这里使用惰性模式匹配

  P f <*> ~(P a) = P b
    where b input = onResult (f input) (\i f' -> f' <$> a i)

工作正常。问题是这里的模式匹配。我不明白...请帮助!

tianjing_anbey 回答:堆栈溢出,其中两个函数在应用解析器中相互调用

  

如果我使用惰性模式匹配P f <*> ~(P a) = ...,则可以正常工作。为什么?

这个问题是discussed recently。您也可以使用newtype代替data来修复它:newtype Parser a = P (Input -> ParseResult a)。(*)

list1的定义想知道<*>的两个解析器参数,但是实际上当第一个解析器失败时(当输入耗尽时),我们不需要知道第二!但是由于我们强制使用它,所以它将强制使用它的第二个参数,并且将强制使用它的第二个解析器,即无穷大。(**)也就是说,p 将在输入耗尽时失败,但是我们有list1 p = (:.) <$> p <*> list p会强制list p,即使前一个p失败时它也不会运行。这就是无限循环的原因,也是您使用惰性模式进行修复的原因。

  

datanewtype在懒惰方面有什么区别?

(*)newtype的类型始终只有一个数据构造函数,并且与之进行模式匹配实际上不会强制该值,因此它隐式地类似于惰性模式。尝试newtype P = P Intlet foo (P i) = 42 in foo undefined,看它是否有效。

(**)当解析器仍在准备,合成时会发生这种情况;在组合的组合解析器甚至无法在实际输入上运行之前。这意味着还有另一种解决问题的方法:定义

list1 p = (:.) <$> p <*> P (\s -> parse (list p) s)

无论<*>的懒惰程度以及是否使用datanewtype,这都应该起作用。

有趣的是,上面的定义意味着解析器将在运行时实际创建,具体取决于输入,输入是Monad的定义特征,而不是应为静态已知的Applicative。但是这里的区别在于,应用程序取决于输入的隐藏状态,而不取决于“返回的”值。

本文链接:https://www.f2er.com/3145180.html

大家都在问