我正在使用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实例更清洁,更难以阅读?我觉得这个问题有一个简单的答案,但我还没有能够绕过这些类型。
答案 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
同时实现了Alternative
和Monad
:
instance Monad Maybe
instance Alternative Maybe
...这意味着StateT s Maybe
自动成为Functor
,Applicative
,Monad
和Alternative
,而我们无需任何额外的工作。< / p>
技巧的最后一部分是GeneralizedNewtypeDeriving
,它允许我们通过newtype包装器提升类型类实例。由于我们的基础StateT
类型是Functor
,Applicative
,Monad
和Alternative
,我们可以通过添加新类型来自动提升所有四种类型类实例:
... deriving (Functor, Applicative, Monad, Alternative)
...编译器会为我们的newtype重新实现它们,注意为我们做所有newtype包装和解包。
因此,如果您想了解如何为解析器实现Applicative
,您可能需要研究Applicative
如何为StateT
实施,然后从中推断如何实现它为您的解析器类型。
答案 1 :(得分:6)
我认为你还没有进入Monad
。您使用collapse
和fmap
的方式告诉我您基本上正在重新发明Monad
来解决此问题,尤其是Monad Maybe
实例。实际上,对于此monad,您的collapse
与join
相同。确实使用 是解决这个问题的一种非常优雅的方式,但在这一点上可能有些“作弊”。以下是我在使用您的功能时可以获得的最佳形状:
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)