Haskell 如何连锁a->;IO(MB)功能

Haskell 如何连锁a->;IO(MB)功能,haskell,types,monads,Haskell,Types,Monads,有一些具有签名的函数,如: a -> IO (m b) b -> IO (m c) c -> IO (m d) a -> IO (m b) b -> IO (m c) c -> IO (m d) 我怎么把它们锁起来 a -> IO (m d) ? 实际应用:假设有一组REST端点。每个函数都返回一个值,下一个函数都需要previous返回的值作为参数 因此,从端点获取的函数如下所示: Value1 -> IO (Maybe Value2) V

有一些具有签名的函数,如:

a -> IO (m b)
b -> IO (m c)
c -> IO (m d)
a -> IO (m b)
b -> IO (m c)
c -> IO (m d)
我怎么把它们锁起来

a -> IO (m d)
?

实际应用:假设有一组REST端点。每个函数都返回一个值,下一个函数都需要previous返回的值作为参数

因此,从端点获取的函数如下所示:

Value1 -> IO (Maybe Value2)
Value2 -> IO (Maybe Value3)
Value3 -> IO (Maybe Value4)
有一些具有签名的函数,如:

a -> IO (m b)
b -> IO (m c)
c -> IO (m d)
a -> IO (m b)
b -> IO (m c)
c -> IO (m d)
我怎么把它们锁起来

a -> IO (m d)
一般来说,您可能无法。例如,如果
m
是,我不确定问这个问题是否有意义。通常,您可能期望
m
成为
Monad
。但是,请注意,即使
m
Monad
,其与
IO
的组合也可能不是()

啊,现在你说话了!您在这里寻找的抽象是和Kleisli组合。然后比如说,

import Control.Monad ((>=>))
import Control.Monad.Trans.Maybe (MaybeT(..))

rest1 :: Value1 -> IO (Maybe Value2)
rest2 :: Value2 -> IO (Maybe Value3)
rest3 :: Value3 -> IO (Maybe Value4)

rest4 :: Value1 -> IO (Maybe Value4)
rest4 x = runMaybeT ((MaybeT . rest1 >=> MaybeT . rest2 >=> MaybeT . rest3) x)
那看起来还是有点难看。要做的事情可能是重构
rest1
rest2
rest3
函数。正如注释中指出的那样,
MaybeT IO a
可以转换为
IO(可能是a)
(事实上,这正是
runMaybeT
MaybeT
所做的)

有一些具有签名的函数,如:

a -> IO (m b)
b -> IO (m c)
c -> IO (m d)
a -> IO (m b)
b -> IO (m c)
c -> IO (m d)
我怎么把它们锁起来

a -> IO (m d)
一般来说,您可能无法。例如,如果
m
是,我不确定问这个问题是否有意义。通常,您可能期望
m
成为
Monad
。但是,请注意,即使
m
Monad
,其与
IO
的组合也可能不是()

啊,现在你说话了!您在这里寻找的抽象是和Kleisli组合。然后比如说,

import Control.Monad ((>=>))
import Control.Monad.Trans.Maybe (MaybeT(..))

rest1 :: Value1 -> IO (Maybe Value2)
rest2 :: Value2 -> IO (Maybe Value3)
rest3 :: Value3 -> IO (Maybe Value4)

rest4 :: Value1 -> IO (Maybe Value4)
rest4 x = runMaybeT ((MaybeT . rest1 >=> MaybeT . rest2 >=> MaybeT . rest3) x)
那看起来还是有点难看。要做的事情可能是重构
rest1
rest2
rest3
函数。正如注释中指出的那样,
MaybeT IO a
可以转换为
IO(可能是a)
(事实上,这正是
runMaybeT
MaybeT
所做的)


如果m有一个
可遍历的实例(
可能有一个),这里有另一个选择:

import           Control.Monad (join)

f :: (Traversable m, Monad m)
  => (a -> IO (m b))
  -> (b -> IO (m c))
  -> (c -> IO (m d))
  -> a
  -> IO (m d)
f fa fb fc a = fa a
           >>= traverse fb
           >>= fmap join . traverse fc . join

如果m有一个
可遍历的实例(
可能有一个),这里有另一个选择:

import           Control.Monad (join)

f :: (Traversable m, Monad m)
  => (a -> IO (m b))
  -> (b -> IO (m c))
  -> (c -> IO (m d))
  -> a
  -> IO (m d)
f fa fb fc a = fa a
           >>= traverse fb
           >>= fmap join . traverse fc . join

您正在寻找Kliesli合成
(>=>)
在这里另外检查
IO(可能是a)~MaybeT IO a
并且
MaybeT
在您正在寻找Kliesli合成
(>=>)
在这里另外检查
IO(可能是a)~MaybeT IO a
MaybeT
在中定义,看起来您不需要
liftIO
,并且
>>=返回。join
fmap join
。对于
>>=返回的零件。加入
我想把最后一步分开。因此,如果想在这个示例中添加更多步骤(即
d->IO(me)
),他们只需添加另一行
>>=traverse fd。在最后一行之前加入。看起来您不需要
liftIO
,并且
>=返回。join
fmap join
。对于
>>=返回的零件。加入
我想把最后一步分开。因此,如果想在这个示例中添加更多步骤(即
d->IO(me)
),他们只需添加另一行
>>=traverse fd。在最后一行之前加入
。重构后,重新生成
rest
函数以重新获得原始函数的味道是否有意义?例如,
rest1::MonadTrans mt=>Value1->mt IO Value2
?@chepner我肯定会说是的;虽然我不确定你的特殊类型是否有意义。类似于
rest1::(MonadIO m,MonadPlus m)=>Value1->m Value2
会表示
rest1
可能会执行一些IO(
MonadIO
),但可能不会成功(
MonadPlus
)。重构后,重新生成
rest
函数,以重新获得原始函数的味道,这有意义吗?例如,
rest1::MonadTrans mt=>Value1->mt IO Value2
?@chepner我肯定会说是的;虽然我不确定你的特殊类型是否有意义。类似于
rest1::(MonadIO m,MonadPlus m)=>Value1->m Value2
的内容表示
rest1
可能会执行一些IO(
MonadIO
),但可能不会成功(
MonadPlus
)。