1

I have a Request type:

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

And I'm parsing it (from a raw HTTP request), as follows:

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

Now, the calls to parseRawRequestType, parseRawRequestPath (etc) can fail. To make my code more resilient, I've changed their type signature from:

parseRawRequestType :: String -> RequestType

to

parseRawRequestType :: String -> Maybe RequestType

But what is the best way to turn parseRawRequest into a Maybe Request ? Do I have to manually check each component (reqType, path, options) for Nothing, or is there a different way that I'm missing?

There must be a way to somehow compose the object creation and Nothing-checking!

I wrote the following, but it feels messy and not ideal: (Untested)

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

Cheers.

haz
  • 2,034
  • 4
  • 28
  • 52
  • 1
    The words "sum type" in the question title are a little confusing, since your `Request` type is a *product* of several Maybe fields, not a sum of them. – amalloy Jul 14 '18 at 23:32
  • You're 100% correct. Edited. – haz Jul 15 '18 at 02:19

1 Answers1

5

This is exactly the pattern that Applicative Functors (Control.Applicative) represent. Applicatives are like regular Functors, but with two extra operations:

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

pure lets you put any value into an applicative, and means that for any applicative, you can write an fmap as fmap f x = pure f <*> x.

The interesting operator in this case is <*>. The idea is that if you have a function "inside" the functor, you can apply it to another value in the functor. If you do Request <$> (_ :: Maybe RequestType), you will get something of type Maybe (String -> [(String, String)] -> Request). The <*> operator will then let you take this and apply it to something of type Maybe String to get Maybe [(String, String)] -> Request) and so on.

Example:

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

Notice however that the function being applied must have type f (a -> b) instead of the a -> m b of the common monadic bind operator. In the effectful context, you can think of this as <*> giving a way to sequence effects without inspecting intermediate results, whereas >>= gives you a little more power (note: the essential difference between the power of an Applicative Functor and a Monad is a function join :: m (m a) -> m a. Can you think how to obtain >>= with <*> and join?). However, Applicatives are a more general interface, which means that you can use them in more cases, and they can sometimes have nice properties when it comes to analysis/optimization. There seems to be a decent overview of Applicatives vs Monads and when you might want to use Applicatives here.

Lucy Maya Menon
  • 1,560
  • 8
  • 15