简体   繁体   中英

Haskell bind with multiple monads

Right now my code looks like this:

postUser :: ServerPart Response
postUser = do
  -- parseBody :: ServerPart (Maybe User)
  parsedUser <- parseBody
  case parsedUser of
    Just user -> do
      -- saveUser :: User -> ServerPart (Maybe (Key User))
      savedUser <- saveUser user
      case savedUser of
        Just user -> successResponse
        Nothing   -> errorResponse "user already exists"
    Nothing   -> errorResponse "couldn't parse user"

Which works, but I know there's a way to avoid the nested pattern matching. I thought this was what bind would do for me, so I tried

parseUser :: ServerPart (Maybe User)
addUser :: User -> ServerPart (Maybe ())
case parseUser >>= saveUser of
  Just _  -> success
  Nothing -> error

and

savedUser <- (parseUser >>= saveUser)
case savedUser of
  Just _  -> success
  Nothing -> error

But I get the following error:

Couldn't match type ‘Maybe a0’ with ‘User’
    Expected type: Maybe a0 -> ServerPartT IO (Maybe (Key User))
      Actual type: User -> ServerPart (Maybe (Key User))
    In the second argument of ‘(>>=)’, namely ‘saveUser’
    In the expression: parseBody >>= saveUser

which I take to mean >>= is applying saveUser to the Maybe User instead of the User that I need it to, and I'm not sure how to finagle the types to match. How can I rewrite this to avoid the nested pattern matching?

While I would argue that the original way you have it written is the most readable approach, taking this as an exercise, the MaybeT monad transformer is what you are looking for.

The problem you are running into is that your are trying to jump between the ServerPart monad and the Maybe monad. This is why you can't directly bind parseBody and saveUser. Monad transformers allow you to combine monads to avoid this problem.

import Control.Monad.Trans.Maybe

postUser :: ServerPart Response
postUser = do
  -- parseBody :: MaybeT ServerPart User
  -- saveUser :: User -> MaybeT ServerPart (Key User)
  user <- runMaybeT $ parseBody >>= saveUser
  case user of
    Just _ -> successResponse
    Nothing -> errorResponse "Error saving user"

You will need to refactor your parseBody and saveUser functions to use the MaybeT monad. Since I can't see these functions, I can't help you there, but it can usually be done easily using lift from Control.Applicative.

Useful links on monad transformers:

https://en.wikibooks.org/wiki/Haskell/Monad_transformers https://www.schoolofhaskell.com/user/commercial/content/monad-transformers

EDIT: MaybeT for parseBody

parseBody :: FromJSON a => MaybeT ServerPart a
parseBody = MaybeT $ fmap A.decode getBody

And just as a general tip: bar >>= (return . f) is equivalent to fmap f bar . The latter being cleaner and more general since it does not require the monad instance.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM