【问题标题】:Is there a "chain" monad function in Haskell?Haskell 中是否有“链”单子函数?
【发布时间】:2015-10-20 03:55:28
【问题描述】:

解释“重复”

有人指出Is this a case for foldM? 可能是重复的。现在,我强烈认为,可以用相同答案回答的两个问题不一定是重复的! “什么是 1 - 2”和“什么是 i^2”都产生“-1”,但不,它们不是重复的问题。我的问题(已经回答了,有点)是关于“函数iterateM 是否存在于 Haskell 标准库中”,而不是“如何实现链式 monad 动作”。

问题

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

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

它只是执行一个单子动作n 次,将前一个结果输入到下一个动作中。我尝试了一些hoogle 搜索和一些谷歌搜索,但没有找到“标准”Haskell 附带的任何内容。有这样预定义的正式函数吗?

【问题讨论】:

  • 我也没有在任何 common 包中找到它 - 但Hayoo 至少知道 2 个地方 - 在这种情况下我会复制我猜它(也许chainM 是一个更好的名字 - replicategiven
  • replicateM 是否输入了上一个结果?
  • 不,它没有——但如果我阅读重复或复制,我会想到不同的东西(仅出于这个原因)——这实际上只是对命名的评论——如果造成误解,抱歉
  • @Carsten,你的评论启发了我的回答。
  • @dfeuer 我不知道 - 我只是搜索了签名并快速浏览了一下 - 我非常喜欢你的;)

标签: haskell monads


【解决方案1】:

您可以使用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

【讨论】:

  • 这很清楚!胜过写三行
【解决方案2】:

Carsten mentioned replicate,这不是一个坏主意。

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

这背后的想法是,对于任何 monad ma -&gt; 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 -&gt; m a 类型的函数,我们实际上是在查看 Kleisli 类别的一个幺半群,因此我们可以考虑折叠这些箭头的列表

上面的代码所做的是将组合运算符折叠起来,翻转成fn 副本列表,并像往常一样以标识结束。翻转组合运算符实际上使我们陷入双重类别;对于许多常见的 monad,x &gt;=&gt; y &gt;=&gt; z &gt;=&gt; ww &lt;=&lt; z &lt;=&lt; y &lt;=&lt; x 更有效;因为在这种情况下所有的箭头都是相同的,看来我们也可以。请注意,对于惰性状态 monad 和可能还有 reader monad,使用未翻转的 &lt;=&lt; 运算符可能会更好; &gt;=&gt; 通常更适合 IOST s 和通常的严格状态。

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

【讨论】:

    【解决方案3】:

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

    从已经给出的答案的另一个角度来看,我指出(s -&gt; m s) 看起来有点像状态类型为s 的状态单子中的动作。

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

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

    > :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 -&gt; do x &lt;- 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 ()
    

    然后你可以复制它 n 次,使用 replicateM_ 版本,因为从 replicateM 返回的列表 [()] 不会很有趣:

    > :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
    

    【讨论】:

    • 这与我的回答非常相似。 StateT 的单子接口是多余的,因为你从不查看结果。假设您使用 newtype StateM s m = StateM {runStateM :: s -&gt; m s} 。然后instance Monad m =&gt; Monoid (StateM s) where {mempty = pure; mappend (StateM f) (StateM g) = StateM (f &gt;=&gt; g)}。现在你可以foldMap StateM 把一堆s -&gt; m s 函数串起来!
    • 当然。到处都有幺半群。 f ()*&gt; 下的幺半群,s -&gt; m s&gt;=&gt; 下的一个幺半群,以此类推。我回答的重点只是将问题与标准库函数replicateM 联系起来,因为乍一看它似乎适用,但事实并非如此。观察结果是“嗯,它是 replicateM - 只是为了稍微不同的 Monad”。
    • 是的,这是有道理的。
    猜你喜欢
    • 1970-01-01
    • 2011-03-17
    • 2013-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-30
    • 1970-01-01
    相关资源
    最近更新 更多