首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >解构` `Maybe (a,b)`

解构` `Maybe (a,b)`
EN

Stack Overflow用户
提问于 2020-12-26 19:49:59
回答 2查看 78关注 0票数 1

类似于我的last question的后续版本。我正在学习Brent Yorgey的Haskell课程,我正在尝试解决一个练习,该练习要求我们为以下类型创建一个Applicative实例:

代码语言:javascript
复制
newtype Parser a = Parser { runParser :: String -> Maybe (a, String) }

runParser解析一个字符串并返回一个令牌和剩余的字符串。在这种情况下,p1 <*> p2应该将runParser p1生成的函数应用于runParser p2生成的令牌(在运行runParser p1之后应用于字符串的左侧)。

到目前为止,我有:

代码语言:javascript
复制
(Parser { runParser = run }) <*> (Parser { runParser = run' }) = Parser run''
  where run'' s = (first <$> f) <*> (s' >>= run')
          where f = fst <$> run s
                s' = snd <$> run s

(first <$> f) <*> (s' >>= run')在我看来相当简洁,但是嵌套的whererun s的奇怪解构看起来有点不对劲。有没有更好的方式来写这个?

EN

回答 2

Stack Overflow用户

发布于 2020-12-26 20:43:21

首先,让我稍微重写一下,以避免模式匹配:

代码语言:javascript
复制
p <*> q = Parser run
  where run s = (first <$> f) <*> (s' >>= runParser q)
          where f = fst <$> runParser p s
                s' = snd <$> runParser p s

在这里,我简单地使用了字段访问器runParser :: Parser a -> String -> Maybe (a, String),而不是直接对参数进行模式匹配。这被认为是在Haskell中访问newtyped函数的一种更常用的方法。

接下来,可以进行一些明显的简化,特别是内联一些函数:

代码语言:javascript
复制
p <*> q = Parser $ \s -> (first <$> f) <*> (s' >>= runParser q)
  where
    f = fst <$> runParser p s
    s' = snd <$> runParser p s

(请注意,现在必须将s显式传递给where块中的函数,以便它们可以访问它。别担心,我会马上把它处理掉的。)

这个实现中令人困惑的一件事是嵌套的应用程序和monads。我将略微重写这一部分,以使其更清晰:

代码语言:javascript
复制
p <*> q = Parser $ \s ->
    let qResult = s' s >>= runParser q
    in first <$> f s <*> qResult
  where
    f s = fst <$> runParser p s
    s' s = snd <$> runParser p s

接下来,让我们去掉那些烦人的fs'定义。我们可以使用模式匹配来做到这一点。通过对runParser p s的输出进行模式匹配,我们可以直接访问这些值:

代码语言:javascript
复制
p <*> q = Parser $ \s ->
    case runParser p s of
        Nothing -> Nothing 
        Just (f, s') ->
            let qResult = runParser q s'
            in first f <$> qOutput

(请注意,由于Maybe中不再有fs',因此以前所需的许多应用程序和单行管道现在都不再需要。一个<$>仍然存在,因为runParser q s'仍然可能失败)。

让我们稍微重写一下,通过内联qResult

代码语言:javascript
复制
p <*> q = Parser $ \s ->
    case runParser p s of
        Nothing -> Nothing 
        Just (f, s') -> first f <$> runParser q s'

现在观察这段代码中的一个模式。它执行runParser p s,如果失败则失败;否则它在另一个可能失败的计算中使用该值。这听起来就像是一元排序!所以让我们用>>=重写它

代码语言:javascript
复制
p <*> q = Parser $ \s -> runParser p s >>= \(f, s') -> first f <$> runParser q s'

最后,为了提高可读性,整个代码可以用do-notation重写:

代码语言:javascript
复制
p <*> q = Parser $ \s -> do
     (f, s') <- runParser p s
     qResult <- runParser q s'
     return $ first f qResult

更容易阅读!这个版本的特别之处在于,它很容易看到发生了什么-运行第一个解析器,获得它的输出,并使用它来运行第二个解析器,然后组合结果。

票数 5
EN

Stack Overflow用户

发布于 2020-12-26 20:43:21

在我看来,只使用基本模式匹配来保持简单,而不过多地依赖于<*><$>first和其他库函数,这并不丢人。

代码语言:javascript
复制
Parser pF <*> Parser pX = Parser $ \s -> do
   (f, s' ) <- pF s
   (x, s'') <- pX s'
   return (f x, s'')

上面的do块位于Maybe monad中。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65456082

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档