Haskell 与多个单子绑定

Haskell bind with multiple monads

现在我的代码如下所示:

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"

这行得通,但我知道有一种方法可以避免嵌套模式匹配。我认为这就是 bind 会为我做的,所以我尝试了

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

savedUser <- (parseUser >>= saveUser)
case savedUser of
  Just _  -> success
  Nothing -> 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

我的意思是 >>=saveUser 应用于 Maybe User 而不是我需要的 User,我不确定如何欺骗要匹配的类型。 我如何重写它以避免嵌套模式匹配?

虽然我认为您编写它的原始方式是最易读的方法,但将其作为练习,您正在寻找的是 MaybeT monad 转换器。

您 运行 遇到的问题是您试图在 ServerPart monad 和 Maybe monad 之间跳转。这就是为什么不能直接绑定 parseBody 和 saveUser 的原因。 Monad 转换器允许您组合 monad 以避免此问题。

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"

您需要重构 parseBody 和 saveUser 函数才能使用 MaybeT monad。由于我看不到这些功能,因此无法在这方面为您提供帮助,但通常可以使用 Control.Applicative 中的 lift 轻松完成。

关于 monad 转换器的有用链接:

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

编辑:parseBody 的 MaybeT

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

一般提示:bar >>= (return . f) 等同于 fmap f bar。后者更简洁、更通用,因为它不需要 monad 实例。