Haskell:运行两个monad,保留第一个monad的结果

时间:2017-02-15 06:25:12

标签: haskell monads

使用Haskell,现在我尝试创建一个像

这样的函数
keepValue :: (Monad m) => m a -> (a -> m b) -> m a

具有以下语义:它应该将monad值应用于返回第二个monad的函数,保留第一个monad的结果,但效果第二个

我有Maybe monad:

的工作函数
keepValueMaybe :: Maybe a -> (a -> Maybe b) -> Maybe a

keepValue ma f = case ma >>= f of
    Nothing -> Nothing
    Just _ -> ma

因此,如果第一个值是Nothing,则该函数不会运行(因此没有第二个副作用),但如果第一个值是Just,则运行该函数(带有一侧)影响)。我保留第二次计算的效果(例如,Nothing生成整个表达式Nothing),但保留原始值。

现在我想知道。它适用于任何monad吗?

它看起来有点内置>>,但我在标准库中找不到任何内容。

3 个答案:

答案 0 :(得分:11)

让我们来看看这个!

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = _

那么我们希望keepValue做什么?好吧,我们应该做的第一件事是使用ma,因此我们可以将其连接到f

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  a <- ma
  _

现在我们的va类型为a,因此我们可以将其传递给f

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  va <- ma
  vb <- f va
  _

最后,我们想要制作va,所以我们可以这样做:

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  va <- ma
  vb <- f va
  return va

这就是我如何编写像这样的任何monadic函数的初稿。然后,我要把它清理干净。首先,一些小事:由于ApplicativeMonad的超类,我更喜欢purereturn;我们没有使用vb;我将v放在名字中。所以对于这个函数的基于do - 符号的版本,我认为最好的选择是

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  a <- ma
  _ <- f a
  pure a

但是,现在我们可以开始更好地实施。首先,我们可以通过显式调用_ <- f va替换(>>)

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  a <- ma
  f a >> pure a

现在,我们可以应用简化。您可能知道我们始终可以将(>>=)pure / return替换为fmap / (<$>):任意pure . f =<< ma,{{1} }或ma >>= pure . f(所有这些都是等效的)可以替换为do a <- ma ; pure $ f a。但是,f <$> ma类型类还有另一种鲜为人知的方法(<$)

  

Functor
  用相同的值替换输入中的所有位置。默认定义为(<$) :: a -> f b -> f a,但可以使用更高效的版本覆盖。

因此,我们对fmap . const采用了类似的替换规则:我们始终可以将(<$)ma >> pure b替换为do ma ; pure b。这给了我们

b <$ ma

我认为这是此功能的最短合理版本!没有任何好的无点技巧可以让它变得更干净;一个指标就是在keepValue :: Monad m => m a -> (a -> m b) -> m a keepValue ma f = do a <- ma a <$ f a 区块的第二行多次使用a

顺便提一下,术语说明:您正在运行两个 monadic actions ,或两个 monadic值;你没有运行*&#34;两个monad&#34;。 monad类似于do - 一种支持Maybe(>>=)的类型构造函数。不要将这些值与类型混合在一起 - 这种术语上的区别有助于使事情更加清晰!

答案 1 :(得分:1)

此结构与>>=的{​​{1}} / >>的定义非常相似。

Monad Maybe

因此您的原始表达式可以简化为

case foo of
    Nothing -> Nothing
    Just _ -> bar

foo >>= \_ -> bar
foo >> bar

这适用于其他monad。

但是,我不认为这实际上是你想要的,因为你可以看到ma >>= f >> ma 发生了两次。相反,从第一个ma绑定中获取值,并将其传递到计算结束。

ma >>=

keepValue ma f = ma >>= \a -> f a >> return a - 注释

do

答案 2 :(得分:1)

You could define:

passThrough f = (>>) <$> f <*> pure

and then inplace of

keepValue ma f

write

ma >>= passThrough f

Then to read a line and print it twice (say) would be

getLine >>= passThrough putStrLn >>= putStrLn