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 实例。
现在我的代码如下所示:
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 实例。