【问题标题】:Better Applicative instance for Parser (Haskell)Parser (Haskell) 的更好的 Applicative 实例
【发布时间】:2014-08-30 23:59:28
【问题描述】:

我正在处理Brent Yorgey Haskell course,但在为 Applicative 定义一个好的实例时遇到了麻烦。解析器定义如下:

newtype Parser a = Parser { runParser :: String -> Maybe (a, String) }

该函数接受一个字符串,解析一定数量的输入,并返回一个 Maybe 元组,其中第一个值是解析器的类型,其余的是字符串的未解析剩余部分。例如,这是一个正整数解析器:

posInt :: Parser Integer
posInt = Parser f
  where
    f xs
      | null ns   = Nothing
      | otherwise = Just (read ns, rest)
      where (ns, rest) = span isDigit xs

任务是为 Parser 创建一个 Applicative 实例。我们从一个 Functor 实例开始(我认为这相对简单):

first :: (a -> b) -> (a,c) -> (b,c)
first f (a, c) = (f a, c)

instance Functor Parser where
  fmap f p = Parser f' 
    where f' s = fmap (first f) $ (runParser p) s

然后我尝试了 Applicative:

collapse (Just (Just a)) = Just a
collapse _ = Nothing

extract (Just a, Just b) = Just (a,b)
extract _ = Nothing

appliedFunc :: Parser (a->b) -> Parser a -> String -> Maybe (b, String)
appliedFunc p1 p2 str = extract (f <*> fmap fst result2, fmap snd result2)
  where result1   = (runParser p1) str
        f         = fmap fst result1
        result2   = collapse $ fmap (runParser p2) $ fmap snd result1

instance Applicative Parser where
  pure a = Parser (\s -> Just (a, s))
  p1 <*> p2 = Parser (appliedFunc p1 p2)

...糟糕。所以我的问题是,我怎样才能让我的 Applicative 实例更干净,更不容易阅读?我觉得这个问题有一个简单的答案,但我还不能完全理解这些类型。

【问题讨论】:

    标签: haskell functor applicative


    【解决方案1】:

    我假设您在课程中还没有到Monads。您使用collapsefmap 的方式向我表明,您实际上是在重新发明Monads 来解决这个问题,特别是Monad Maybe 实例。事实上,对于这个 monad,您的 collapsejoin 相同。确实使用 是解决这个问题的一种非常优雅的方法,但在这一点上可能有点“作弊”。以下是我在使用您的功能时可以得到的最佳形状:

    appliedFunc p1 p2 str = collapse $ fmap step1 (runParser p1 str)
      where
        step1 (f, str2) = collapse $ fmap step2 (runParser p2 str2)
          where
            step2 (x, str3) = Just (f x, str3)
    

    一旦你得到正确的Monads,你应该能够用更简洁的(&gt;&gt;=)运算符和/或do符号重写它。

    另一个几乎同样简单但不需要重新发明 monad 的替代方法是使用 Maybes 的显式模式匹配。然后你可以得到类似的东西:

    appliedFunc p1 p2 str = case runParser p1 str of
        Nothing        -> Nothing
        Just (f, str2) -> case runParser p2 str2 of
            Nothing        -> Nothing
            Just (x, str3) -> Just (f x, str3)
    

    【讨论】:

    • @AndrewC 对于一个练习,您可能是对的,但有一个与 Gabriel Gonzalez 的回答相关的更深层次的问题:StateT m 不是Applicative,除非m 是完整的Monad。这在变压器之间有所不同:MaybeT m 还需要完整的 MonadReaderT mWriterT m 只需要 Applicative,而著名的 ContT m 设法获得完整的 Monadnom 的要求。
    • Gabriel Gonzalez 的回答总是很棒,我也已经对他的回答投了赞成票。这段代码如此简洁明了,这对 monad 转换器来说是一个引人注目的广告。当然,正如您正确指出的那样,我的评论中的错误是连接发生在哪个级别,这给我带来了不小的尴尬,特别是因为我之前曾向另一个提问者解释过为什么在他们想要的时候,解析器内部需要 monad始终只使用应用程序!哇!
    【解决方案2】:

    这可能不是你想要的,但我想顺便提一下,有一种非常简洁的方式来实现它:

    {-# LANGUAGE GeneralizedNewtypeDeriving #-}
    
    import Control.Applicative
    import Control.Monad.Trans.State
    
    newtype Parser a = Parser { unParser :: StateT String Maybe a }
        deriving (Functor, Applicative, Monad, Alternative)
    
    runParser :: Parser a -> String -> Maybe (a, String)
    runParser = runStateT . unParser
    
    parser :: (String -> Maybe (a, String)) -> Parser a
    parser = Parser . StateT
    

    之所以有效,是因为 StateT 在底层实现为:

    newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
    

    如果您将s 专精到String 并将m 专精到Maybe,您会得到:

    StateT String Maybe a  ~  String -> Maybe (a, String)
    

    ...和你的类型一样。

    StateT 自动为您提供以下实例:

    instance Monad m => Functor     (StateT s m)
    instance Monad m => Applicative (StateT s m)
    instance Monad m => Monad       (StateT s m)
    
    instance Alternative m => Alternative (StateT s m)
    

    ...我们可以在这些情况下将m 专门化为Maybe,因为Maybe 实现了AlternativeMonad

    instance Monad Maybe
    
    instance Alternative Maybe
    

    ...这意味着StateT s Maybe 自动成为FunctorApplicativeMonadAlternative,无需我们进行任何额外的工作。

    技巧的最后一部分是GeneralizedNewtypeDeriving,它允许我们通过新类型包装器提升类型类实例。由于我们底层的StateT 类型是FunctorApplicativeMonadAlternative,我们可以通过添加以下新类型自动提升所有四个类型类实例:

    ... deriving (Functor, Applicative, Monad, Alternative)
    

    ...编译器将为我们的新类型重新实现它们,并为我们完成所有新类型的包装和解包。

    因此,如果您想弄清楚如何为您的解析器实现Applicative,您可能需要研究如何为Applicative 实现StateT,然后从中推断出如何为您的解析器类型实现它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-11
      • 2011-11-05
      相关资源
      最近更新 更多