如何将 bind 与嵌套的 monad 一起使用?
How to use bind with nested monads?
我有两个函数,一个尝试从网络服务获取令牌但可能失败,另一个尝试使用此令牌获取用户名但可能失败。
getToken :: IO (Maybe Token)
getUsername :: Token -> IO (Maybe String)
我想获取 getToken 的结果并将其提供给 getUsername
。如果只有 IO
或 Maybe
,我可以简单地使用 bind,但由于有向下嵌套的 monad,我不能。我怎样才能写出等同于 getToken >>= getUsername :: IO (Maybe String)
的东西?
更一般地说,什么函数的类型是 m1 m2 a -> (a -> m1 m2 b) -> m1 m2 b
?
奖金问题:我如何在 IO
上下文中使用 do 表示法来做到这一点?
我定义了一个函数 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
如果你不想使用do
表示法,那么你可以使用:
useToken2 :: IO (Maybe String)
useToken2 = getToken >>= \token -> maybe (return Nothing) getUsername token
或者使用 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
注意,你也可以直接在monad transformer里面lift IO操作。正如@Robedino 指出的那样,现在没有 do 符号的代码将更加简洁:
useToken :: MaybeT IO String
useToken = getToken >>= getUsername
正如评论中的人所建议的那样,您应该只使用 monad 转换器。
但是你可以在你的情况下避免这种情况。 Monads 一般不通勤,所以你不能用这个签名写一个函数
bind' :: (Monad m, Monad n) => m (n a) -> (a -> m (n b)) -> m (n b)
但是一切正常,如果内部 monad 是 Traversable
class:
的一个实例
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)
而 Maybe
monad 是;因此
type Token = String
getToken :: IO (Maybe Token)
getToken = undefined
getUsername :: Token -> IO (Maybe String)
getUsername = undefined
useToken :: IO (Maybe String)
useToken = getToken `bindT` getUsername
此外,使用 {-# LANGUAGE RebindableSyntax #-}
你可以写
(>>=) = bindT
useToken :: IO (Maybe String)
useToken = do
x <- getToken
getUsername x
更新
与类型级别的组合
newtype (f :. g) a = Nested { runNested :: f (g a) }
您可以为嵌套的 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)
你的例子是
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
或者像使用 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
我有两个函数,一个尝试从网络服务获取令牌但可能失败,另一个尝试使用此令牌获取用户名但可能失败。
getToken :: IO (Maybe Token)
getUsername :: Token -> IO (Maybe String)
我想获取 getToken 的结果并将其提供给 getUsername
。如果只有 IO
或 Maybe
,我可以简单地使用 bind,但由于有向下嵌套的 monad,我不能。我怎样才能写出等同于 getToken >>= getUsername :: IO (Maybe String)
的东西?
更一般地说,什么函数的类型是 m1 m2 a -> (a -> m1 m2 b) -> m1 m2 b
?
奖金问题:我如何在 IO
上下文中使用 do 表示法来做到这一点?
我定义了一个函数 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
如果你不想使用do
表示法,那么你可以使用:
useToken2 :: IO (Maybe String)
useToken2 = getToken >>= \token -> maybe (return Nothing) getUsername token
或者使用 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
注意,你也可以直接在monad transformer里面lift IO操作。正如@Robedino 指出的那样,现在没有 do 符号的代码将更加简洁:
useToken :: MaybeT IO String
useToken = getToken >>= getUsername
正如评论中的人所建议的那样,您应该只使用 monad 转换器。
但是你可以在你的情况下避免这种情况。 Monads 一般不通勤,所以你不能用这个签名写一个函数
bind' :: (Monad m, Monad n) => m (n a) -> (a -> m (n b)) -> m (n b)
但是一切正常,如果内部 monad 是 Traversable
class:
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)
而 Maybe
monad 是;因此
type Token = String
getToken :: IO (Maybe Token)
getToken = undefined
getUsername :: Token -> IO (Maybe String)
getUsername = undefined
useToken :: IO (Maybe String)
useToken = getToken `bindT` getUsername
此外,使用 {-# LANGUAGE RebindableSyntax #-}
你可以写
(>>=) = bindT
useToken :: IO (Maybe String)
useToken = do
x <- getToken
getUsername x
更新
与类型级别的组合
newtype (f :. g) a = Nested { runNested :: f (g a) }
您可以为嵌套的 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)
你的例子是
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
或者像使用 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