Parser(Haskell)更好的应用实例

时间:2014-08-30 23:59:29

标签: haskell functor applicative

我正在使用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实例更清洁,更难以阅读?我觉得这个问题有一个简单的答案,但我还没有能够绕过这些类型。

2 个答案:

答案 0 :(得分:6)

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

{-# 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,而我们无需任何额外的工作。< / p>

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

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

...编译器会为我们的newtype重新实现它们,注意为我们做所有newtype包装和解包。

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

答案 1 :(得分:6)

我认为你还没有进入Monad。您使用collapsefmap的方式告诉我您基本上正在重新发明Monad来解决此问题,尤其是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)

一旦你到达Monad,你应该能够用更简洁的(>>=)运算符和/或do表示法重写它。

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

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)
相关问题