作为Maybe零件的结果创建Maybe类型的最佳方法

时间:2018-07-14 00:21:57

标签: haskell monads 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
  }

现在,对parseRawRequestTypeparseRawRequestPath(等等)的调用可能会失败。为了使我的代码更具弹性,我将其类型签名从以下位置更改:

parseRawRequestType :: String -> RequestType

parseRawRequestType :: String -> Maybe RequestType

但是将parseRawRequest变成Maybe Request的最好方法是什么?我是否需要手动检查reqType的每个组件(pathoptionsNothing),还是缺少其他方法?

必须有某种方法可以组成对象创建和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

干杯。

1 个答案:

答案 0 :(得分:5)

这正是应用函子(Control.Applicative)表示的模式。应用程序就像常规的函子一样,但是有两个额外的操作:

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

pure使您可以将任何值放入应用程序中,这意味着对于任何应用程序,您都可以将fmap编写为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类型。在有效的上下文中,您可以认为这是<*>,它提供了一种排序效果的方式,而无需检查中间结果,而>>=则为您提供了更多功能(请注意:应用函子和Monad是join :: m (m a) -> m a函数。您能考虑如何通过>>=<*>获得join吗?)。但是,应用程序是一个更通用的界面,这意味着您可以在更多情况下使用它们,并且在进行分析/优化时,它们有时可能具有不错的属性。似乎有相当不错的Applicatives vs Monads概述,以及何时可能要使用Applicatives here