anj*_*ruu 7 haskell functor applicative
我正在完成Brent Yorgey Haskell课程,而我在为Applicative定义一个好的实例时遇到了麻烦.解析器定义如下:
newtype Parser a = Parser { runParser :: String -> Maybe (a, String) }
Run Code Online (Sandbox Code Playgroud)
该函数接受一个字符串,解析一定量的输入,并返回一个Maybe元组,其中第一个值是解析器的类型,其余的是未解析的字符串剩余部分.例如,这是正整数的解析器:
posInt :: Parser Integer
posInt = Parser f
where
f xs
| null ns = Nothing
| otherwise = Just (read ns, rest)
where (ns, rest) = span isDigit xs
Run Code Online (Sandbox Code Playgroud)
赋值是为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
Run Code Online (Sandbox Code Playgroud)
然后我尝试使用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)
Run Code Online (Sandbox Code Playgroud)
......呸.所以我的问题是,如何让我的Applicative实例更清洁,更难以阅读?我觉得这个问题有一个简单的答案,但我还没能绕过这些类型.
这可能不是你想要的,但我想顺便提一下,有一个非常简洁的方法来实现这个:
{-# 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
Run Code Online (Sandbox Code Playgroud)
这样做的原因是引擎盖下的StateT
实现方式如下:
newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
Run Code Online (Sandbox Code Playgroud)
如果您专注s
于String
并专注m
于Maybe
,您将得到:
StateT String Maybe a ~ String -> Maybe (a, String)
Run Code Online (Sandbox Code Playgroud)
...与您的类型相同.
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)
Run Code Online (Sandbox Code Playgroud)
...我们可以专注m
于那些实例,Maybe
因为Maybe
实现了两者Alternative
和Monad
:
instance Monad Maybe
instance Alternative Maybe
Run Code Online (Sandbox Code Playgroud)
......这样就意味着StateT s Maybe
自动为Functor
,Applicative
,Monad
并Alternative
没有对我们进行任何额外的工作.
这个技巧的最后一部分是GeneralizedNewtypeDeriving
,它允许我们通过newtype包装器提升类型类实例.因为我们的基本StateT
类型是Functor
,Applicative
,Monad
,和Alternative
,我们可以通过添加自动升降,通过我们的NEWTYPE所有四个类型的类实例:
... deriving (Functor, Applicative, Monad, Alternative)
Run Code Online (Sandbox Code Playgroud)
...并且编译器将为我们的newtype重新实现它们,注意为我们做所有newtype包装和解包.
因此,如果您想弄清楚如何Applicative
为您的解析器实现,您可能想要研究如何Applicative
实现StateT
,然后从中推断如何为您的解析器类型实现它.
我假设你还没有Monad
参加课程.您正在使用的方式collapse
并fmap
告诉我您基本上正在重新发明Monad
以解决此问题,特别是Monad Maybe
实例.事实上,你collapse
和join
这个monad 是一样的.确实使用它是解决这个问题的一种非常优雅的方式,但在这一点上可能有点"作弊".以下是我在使用您的功能时可以获得的最佳形状:
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)
Run Code Online (Sandbox Code Playgroud)
一旦你做到了Monad
正确,你应该能够用更简洁的(>>=)
运算符和/或do
表示法重写它.
另一种几乎同样简单,但不需要重新发明monad的替代方案是使用Maybe
s的显式模式匹配.然后你可以得到类似的东西:
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)
Run Code Online (Sandbox Code Playgroud)