Question

I'm applying my (limited) Haskell knowledge to the Snap web framework and seeing what I can build. I'm trying to get a (possibly non-existent) parameter and parse it into an int. Obviously "Maybe" is what I'll be wanting.

In the code below AppHandler is defined as Handler App App (a monad with two levels of state I think, although I can't find anything in the tutorial now). The B8 is ByteString.Char8 and readInt returns Maybe(Int,ByteString)

The code below works, but presumably there should be a way to chain the maybe calls together (via MaybeT presumably, since I'm in a Monad already). The chaining makes particular sense because the next step will be to fetch a row from the database based on the parsed id, so of course that will return a "Maybe a" too. Clearly that's a pattern that's going to be very common.

-- Given a parameter name, return the Int it points to
-- or Nothing if there is some problem
maybeIntParam :: ByteString -> AppHandler (Maybe Int)
maybeIntParam pname = do
    raw_param <- getParam pname
    let mb_i_b = maybe (Nothing) B8.readInt raw_param
    case mb_i_b of
        Nothing    -> return Nothing
        Just (p,_) -> return $ Just p

I tried applying runMaybeT but with no real understanding of what types need changing frankly I was making random changes in the hope the error goes away. It didn't, although it changed and moved around from line to line.

I'm treating this as progress, since I'm now completely lost at a much higher level than I was when I started exploring Haskell...


Edit: walking through kosmikus' answer, hopefully I've understood it...

1 maybeIntParam :: ByteString -> AppHandler (Maybe Int)
2 maybeIntParam pname = do
3     raw_param <- getParam pname
4     return $ do
5       param  <- raw_param
6       (p, _) <- B8.readInt param
7       return p

I think I was trying to inch towards this but kept trying to force getParam inside the same block as the other steps.

On line 3, the call to getParam is taking place in the AppHandler. We've got raw_param which is a Maybe ByteString. On line 5 we're in a nested do, so the binding(?) is taking place inside the Maybe monad and param will either be a ByteString or we get Nothing and the rest of the do-block will short-circuit*. Likewise on line 6, p is either an Int or we short-circuit.

All being well, on line 6, p contains an Int (say 42), and line 7 will return Just 42. Back at line 4 that becomes AppHandler (Just 42). Don't need to care what an AppHandler is at the moment - the types are all happy.

Here are some variations that also type-check and may prove of use to those trying to think this through.

maybeIntParam1 :: ByteString -> AppHandler (Maybe Int)
maybeIntParam1 pname = do
    raw_param <- getParam pname
    let mb_int_param = do
        param  <- raw_param
        (p, _) <- B8.readInt param
        return p
    return mb_int_param

maybeIntParam2 :: ByteString -> AppHandler (Maybe Int)
maybeIntParam2 pname = do
    return $ return 27

maybeIntParam3 :: ByteString -> AppHandler (Maybe Int)
maybeIntParam3 pname = do
    return (Just 27)

The non-do variation actually seems simpler in this case. the only bit that needs thinking about is the <$> which if I'm reading right is just fmap and applies the fst to Maybe (Int,ByteString) so we can get Maybe Int.


* If I understand correctly, each subsequent line must be visited but just returns Nothing, so not actually a goto-style shortcut. Edit2: see kosmikus' comment below - laziness + right-nesting means we don't need to evaluate each line.

Was it helpful?

Solution

You can just locally use the Maybe monad here:

maybeIntParam :: ByteString -> AppHandler (Maybe Int)
maybeIntParam pname = do
    raw_param <- getParam pname
    return $ do
      param  <- raw_param
      (p, _) <- B8.readInt param
      return p

Or if you prefer, you can write the Maybe-computations as a one-liner:

maybeIntParam :: ByteString -> AppHandler (Maybe Int)
maybeIntParam pname = do
    raw_param <- getParam pname
    return $ fst <$> (raw_param >>= B8.readInt)

Some of the types involved:

raw_param                          :: Maybe ByteString
B8.readInt                         :: ByteString -> Maybe (Int, ByteString)
raw_param >>= B8.readInt           :: Maybe (Int, ByteString)
fst                                :: (Int, ByteString) -> Int
fst <$> (raw_param >>= B8.readInt) :: Maybe Int
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top