Haskell 中是否有 "chain" monad 函数?

Is there a "chain" monad function in Haskell?

解释一个"duplicate"

有人指出 Is this a case for foldM? 可能是重复的。现在,我有一个强烈的看法,可以用相同答案回答的两个问题不一定是重复的! "What is 1 - 2" 和 "What is i^2" 都产生“-1”,但不,它们不是重复的问题。我的问题(已经回答了)是关于 "whether the function iterateM exists in Haskell standard library",而不是 "How to implement a chained monad action"。

问题

当我写一些项目时,我发现自己写了这个组合器:

repeatM :: Monad m => Int -> (a -> m a) -> a -> m a
repeatM 0 _ a = return a
repeatM n f a = (repeatM (n-1) f) =<< f a

它只是执行一个 monadic 动作 n 次,将之前的结果输入到下一个动作中。我尝试了一些 hoogle 搜索和一些 Google 搜索,但没有找到 "standard" Haskell 附带的任何内容。有这样一个预定义的形式函数吗?

您可以使用foldM,例如:

import Control.Monad

f a = do print a; return (a+2)

repeatM n f a0 = foldM (\a _ -> f a) a0 [1..n]

test = repeatM 5 f 3
  -- output: 3 5 7 9 11

Carsten replicate,这个想法不错。

import Control.Monad
repeatM n f = foldr (>=>) pure (replicate n f)

这背后的想法是,对于任何 monad m,类型 a -> m b 的函数形成 m 的 Kleisli 范畴,带有标识箭头

pure :: a -> m a

(也称为return

和组合运算符

(<=<) :: (b -> m c) -> (a -> m b) -> a -> m c
f <=< g = \a -> f =<< g a

由于我们实际上是在处理 a -> m a 类型的函数,我们实际上是在看 Kleisli 类别的一个幺半群,所以我们可以考虑折叠 lists这些箭头。

上面的代码所做的是折叠组合运算符,将其翻转到 fn 副本列表中,像往常一样以标识结束。翻转组合运算符实际上将我们置于对偶类别中;对于许多常见的单子,x >=> y >=> z >=> ww <=< z <=< y <=< x 更有效;因为在这种情况下所有的箭头都是 相同的 ,看来我们也可以。请注意,对于惰性状态 monad 以及 reader monad,最好使用未翻转的 <=< 运算符; >=> 通常对 IOST s 和通常的严格状态更好。

注意:我不是范畴论者,所以上面的解释可能有错误。

我发现自己经常想要这个功能,我希望它有一个标准的名字。然而,该名称不会是 repeatM - 这将是无限重复的,例如 forever 如果它存在,只是为了与其他库保持一致(并且 repeatM 在某些库中以这种方式定义).

正如已经给出的答案的另一个观点,我指出 (s -> m s) 看起来有点像状态类型为 s.

的 State monad 中的一个动作

事实上,它与 StateT s m () 同构 - 一个 return 没有价值的动作,因为它所做的所有工作都封装在它改变状态的方式中。在这个 monad 中,你真正想要的函数是 replicateM。你可以在 haskell 中这样写,虽然它可能看起来比直接写它更难看。

首先将 s -> m s 转换为 StateT 使用的等效形式,添加无信息 (),使用 liftM 将函数映射到 return类型。

> :t \f -> liftM (\x -> ((),x)) . f
\f -> liftM (\x -> ((),x)) . f :: Monad m => (a -> m t) -> a -> m ((), t)

(本可以使用 fmap 但 Monad 约束在这里似乎更清晰;如果您愿意,本可以使用 TupleSections;如果您发现 do 表示法更易于阅读,它只是 \f s -> do x <- f s; return ((),s) ).

现在这有正确的类型可以用 StateT 结束:

> :t StateT . \f -> liftM (\x -> ((),x)) . f
StateT . \f -> liftM (\x -> ((),x)) . f :: Monad m => (s -> m s) -> StateT s m ()

然后您可以使用 replicateM_ 版本复制它 n 次,因为来自 replicateM 的 returned 列表 [()] 不会有趣:

> :t \n -> replicateM_ n . StateT . \f -> liftM (\x -> ((),x)) . f
\n -> replicateM_ n . StateT . \f -> liftM (\x -> ((),x)) . f :: Monad m => Int -> (s -> m s) -> StateT s m ()

最后你可以使用 execStateT 回到你最初工作的 Monad:

runNTimes :: Monad m => Int -> (s -> m s) -> s -> m s
runNTimes n act =
  execStateT . replicateM_ n . StateT . (\f -> liftM (\x -> ((),x)) . f) $ act