首页 > 解决方案 > 作为 Maybe 部分的结果创建 Maybe 类型的最佳方法

问题描述

我有一个Request类型:

data Request =
  Request {
     reqType :: RequestType,
     path    :: String,
     options :: [(String, String)]
  } deriving Show

我正在解析它(来自原始HTTP请求),如下所示:

parseRawRequest :: String -> Request
parseRawRequest rawReq =
    Request {
        reqType = parseRawRequestType rawReq,
        path    = parseRawRequestPath rawReq,
        options = parseRawRequestOps  rawReq
  }

parseRawRequestType现在,对, parseRawRequestPath(etc)的调用可能会失败。为了使我的代码更具弹性,我将它们的类型签名从:

parseRawRequestType :: String -> RequestType

parseRawRequestType :: String -> Maybe RequestType

但是parseRawRequest变成的最好方法是Maybe Request什么?我是否必须手动检查每个组件(reqType, path, optionsNothing,或者是否有我遗漏的不同方式?

必须有一种方法可以以某种方式组合对象创建和Nothing检查!

我写了以下内容,但感觉很乱且不理想:(未经测试)

parseRawRequest :: String -> Maybe Request
parseRawRequest rawReq
  | Nothing `elem` [reqType, path, options] = Nothing
  | otherwise                               =
    Just Request { reqType=reqType, path=path, options=options }
  where reqType = parseRawRequestType rawReq
        path    = parseRawRequestPath rawReq
        options = parseRawRequestOps  rawReq

干杯。

标签: haskellmonadsmaybe

解决方案


这正是 Applicative Functors ( Control.Applicative) 所代表的模式。Applicatives 就像普通的 Functors,但是有两个额外的操作:

pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b

pure允许您将任何值放入应用程序中,这意味着对于任何应用程序,您都可以编写fmapas fmap f x = pure f <*> x

在这种情况下,有趣的运算符是<*>。这个想法是,如果你在函子“内部”有一个函数,你可以将它应用于函子中的另一个值。如果你这样做Request <$> (_ :: Maybe RequestType),你会得到一些类型的东西Maybe (String -> [(String, String)] -> Request)。然后,<*>操作员将让您将其应用到Maybe String要获取的类型上Maybe [(String, String)] -> Request),依此类推。

例子:

data RequestType
data Request =
  Request { reqType :: RequestType, path :: String, options :: [(String, String)] }

parseRawRequestType :: String -> Maybe RequestType
parseRawRequestType = undefined
parseRawRequestPath :: String -> Maybe String
parseRawRequestPath = undefined
parseRawRequestOps :: String -> Maybe [(String, String)]
parseRawRequestOps = undefined
parseRawRequest :: String -> Maybe Request
parseRawRequest rawReq = Request <$> parseRawRequestType rawReq
                                 <*> parseRawRequestPath rawReq
                                 <*> parseRawRequestOps rawReq

但是请注意,所应用的函数必须具有类型f (a -> b),而不是a -> m b常见的一元绑定运算符。在有效的上下文中,您可以将其视为<*>一种无需检查中间结果即可对效果进行排序的方法,而>>=为您提供了更多功能(注意:Applicative Functor 和 Monad 的功能之间的本质区别是 function join :: m (m a) -> m a。可以你认为如何获得和>>=?)。但是,Applicative 是一个更通用的接口,这意味着您可以在更多情况下使用它们,并且在分析/优化方面有时它们可​​以具有很好的属性。似乎对 Applicatives 与 Monads 有一个不错的概述,以及何时您可能想在此处使用 Applicatives<*>join.


推荐阅读