如何使用绑定嵌套monad?

mad*_*jar 11 monads haskell

我有两个函数,一个尝试从Web服务获取令牌并可能失败,另一个尝试使用此令牌获取用户名并可能失败.

getToken :: IO (Maybe Token)
getUsername :: Token -> IO (Maybe String)
Run Code Online (Sandbox Code Playgroud)

我想取getToken的结果并将其提供给getUsername.如果只有IOor Maybe,我可以简单地使用bind,但由于有嵌套monad,我不能.我怎么能写出相当于的东西getToken >>= getUsername :: IO (Maybe String)

更一般地说,什么功能有类型m1 m2 a -> (a -> m1 m2 b) -> m1 m2 b

额外的问题:我如何在IO上下文中使用do notation做到这一点?

Sib*_*ibi 10

我已经定义了一个useToken显示你的用例的函数:

type Token = String

getToken :: IO (Maybe Token)
getToken = undefined

getUsername :: Token -> IO (Maybe String)
getUsername = undefined

useToken :: IO (Maybe String)
useToken = do
  token <- getToken
  case token of
    Just x -> getUsername x
    Nothing -> return Nothing
Run Code Online (Sandbox Code Playgroud)

如果您不想使用do表示法,那么您可以使用:

useToken2 :: IO (Maybe String)
useToken2 = getToken >>= \token -> maybe (return Nothing) getUsername token
Run Code Online (Sandbox Code Playgroud)

或者使用monad变换器,您的代码将变得更简单:

import Control.Monad.Trans.Maybe
type Token = String

getToken :: MaybeT IO Token
getToken = undefined

getUsername :: Token -> MaybeT IO String
getUsername = undefined

useToken :: MaybeT IO String 
useToken = do
  token <- getToken
  getUsername token
Run Code Online (Sandbox Code Playgroud)

请注意,您也可以直接提升monad变压器内的IO操作.正如@Robedino指出的那样,现在代码将更加简洁而不用表示法:

useToken :: MaybeT IO String 
useToken = getToken >>= getUsername
Run Code Online (Sandbox Code Playgroud)


use*_*465 6

正如评论中的人所建议的那样,您应该只使用 monad 转换器。

但是,您可以在您的情况下避免这种情况。Monads 一般不通勤,所以你不能用这个签名写一个函数

bind' :: (Monad m, Monad n) => m (n a) -> (a -> m (n b)) -> m (n b)
Run Code Online (Sandbox Code Playgroud)

但是一切正常,如果内部 monad 是Traversable类的实例:

import Data.Traversable as T
import Control.Monad

joinT :: (Monad m, Traversable t, Monad t) => m (t (m (t a))) -> m (t a)
joinT = (>>= liftM join . T.sequence)

liftMM :: (Monad m, Monad n) => (a -> b) -> m (n a) -> m (n b)
liftMM = liftM . liftM

bindT :: (Monad m, Traversable t, Monad t) => m (t a) -> (a -> m (t b)) -> m (t b)
bindT x f = joinT (liftMM f x)
Run Code Online (Sandbox Code Playgroud)

Maybe单子是; 因此

type Token = String

getToken :: IO (Maybe Token)
getToken = undefined

getUsername :: Token -> IO (Maybe String)
getUsername = undefined

useToken :: IO (Maybe String)
useToken = getToken `bindT` getUsername
Run Code Online (Sandbox Code Playgroud)

另外,{-# LANGUAGE RebindableSyntax #-}你可以写

(>>=) = bindT

useToken :: IO (Maybe String)
useToken = do
    x <- getToken
    getUsername x
Run Code Online (Sandbox Code Playgroud)

更新

使用类型级撰写

newtype (f :. g) a = Nested { runNested :: f (g a) }
Run Code Online (Sandbox Code Playgroud)

您可以为嵌套的 monad 定义一个 monad 实例:

instance (Monad m, Traversable t, Monad t) => Monad (m :. t) where
    return  = Nested . return . return
    x >>= f = Nested $ runNested x `bindT` (runNested . f)
Run Code Online (Sandbox Code Playgroud)

你的例子是

type Token = String

getToken :: IO (Maybe Token)
getToken = undefined

getUsername :: Token -> IO (Maybe String)
getUsername = undefined

useToken :: IO (Maybe String)
useToken = runNested $ Nested getToken >>= Nested . getUsername
Run Code Online (Sandbox Code Playgroud)

或者就像你对MaybeT变压器所做的那样:

type Nested = (:.)

type Token = String

getToken :: Nested IO Maybe Token
getToken = undefined

getUsername :: Token -> Nested IO Maybe String
getUsername = undefined

useToken :: Nested IO Maybe String
useToken = getToken >>= getUsername

runUseToken :: IO (Maybe String)
runUseToken = runNested useToken
Run Code Online (Sandbox Code Playgroud)