简体   繁体   English

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

[英]Haskell: Run two monads, keep the result of the first one

Playing with Haskell and now I try to create a function like 和Haskell一起玩,现在我尝试创建一个类似的函数

keepValue :: (Monad m) => m a -> (a -> m b) -> m a

with following semantic: it should apply monad value to a function, which return the second monad, and keep the result of the first monad, but the effect of the second one 具有以下语义:它应该将monad值应用于函数,该函数返回第二个monad,并保留第一个monad的结果 ,但是第二个monad的效果

I have a working function in case of Maybe monad: 对于Maybe monad,我有一个工作函数:

keepValueMaybe :: Maybe a -> (a -> Maybe b) -> Maybe a

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

So if the first value is Nothing , the function is not run (so no second side-effect), but if the first value is Just , then, the function is run (with side effect). 因此,如果第一个值为Nothing ,则不运行该函数(因此没有第二个副作用),但如果第一个值为Just ,则运行该函数(带副作用)。 I keep effect of the second computation (eg, Nothing makes the whole expression Nothing ), but the original value. 我保持第二次计算的效果 (例如, Nothing使整个表达式为Nothing ),但是原始值。

Now I wonder. 现在我好奇。 Can it work for any monad? 它适用于任何monad吗?

It looks kinda built-in >> , but I couldn't find anything in standard library. 它看起来有点内置>> ,但我在标准库中找不到任何东西。

Let's walk through this! 让我们来看看吧!

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

So what do we want keepValue to do? 那么我们想要keepValue做什么呢? Well, the first thing we should do is use ma , so we can connect it to f . 好吧,我们应该做的第一件事是使用ma ,所以我们可以将它连接到f

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

Now we have a value va of type a , so we can pass it to f . 现在我们有一个类型a a的值va ,所以我们可以将它传递给f

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

And finally, we want to produce va , so we can just do that: 最后,我们想要生成va ,所以我们可以这样做:

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

This is how I'd walk through writing the first draft of any monadic function like this. 这就是我如何编写像这样的任何monadic函数的初稿。 Then, I'd clean it up. 然后,我会把它清理干净。 First, some small things: since Applicative is a superclass of Monad , I prefer pure to return ; 首先,一些小事:因为ApplicativeMonad的超类,我更喜欢purereturn ; we didn't use vb ; 我们没有使用vb ; and I'd drop the v in the names. 我会删除名字中的v So for a do -notation based version of this function, I think the best option is 因此,对于一个do此功能的-notation基础版本,我认为最好的办法是

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

Now, however, we can start to make the implementation better. 但是,现在我们可以开始更好地实施。 First, we can replace _ <- f va with an explicit call to (>>) : 首先,我们可以用显式调用(>>)替换_ <- f va

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

And now, we can apply a simplification. 现在,我们可以应用简化。 You may know that we can always replace (>>=) plus pure / return with fmap / (<$>) : any of pure . f =<< ma 您可能知道我们总是可以用fmap / (<$>)替换(>>=)加上pure / return :任何pure . f =<< ma pure . f =<< ma , ma >>= pure . f pure . f =<< mama >>= pure . f ma >>= pure . f , or do a <- ma ; pure $ fa ma >>= pure . f ,或do a <- ma ; pure $ fa do a <- ma ; pure $ fa (all of which are equivalent) can be replaced by f <$> ma . do a <- ma ; pure $ fa (所有这些都是等价的)可以用f <$> ma替换。 However, the Functor type class has another, less-well-known method, (<$) : 但是, Functor类型类有另一个不太知名的方法(<$)

(<$) :: a -> fb -> fa
Replace all locations in the input with the same value. 用相同的值替换输入中的所有位置。 The default definition is fmap . const 默认定义是fmap . const fmap . const , but this may be overridden with a more efficient version. fmap . const ,但这可能会被更高效的版本覆盖。

So we have a similar replacement rule for (<$) : we can always replace ma >> pure b or do ma ; pure b 所以我们对(<$)有一个类似的替换规则:我们总是可以替换ma >> pure bdo ma ; pure b do ma ; pure b with b <$ ma . do ma ; pure bb <$ ma This gives us 这给了我们

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

And I think this is the shortest reasonable version of this function! 而且我认为这是此功能的最短合理版本! There aren't any nice point-free tricks to make this cleaner; 没有任何好的无点技巧可以让它变得更干净; one indicator of that is the multiple use of a on the second line of the do block. 一个指标是在do块的第二行上多次使用a


Incidentally, a terminology note: you're running two monadic actions , or two monadic values ; 顺便提一下,术语说明:你正在运行两个monadic动作 ,或两个monadic值 ; you're not running *"two monads". 你没有运行*“两个单子”。 A monad is something like Maybe – a type constructor which supports (>>=) and return . monad就像Maybe一种支持(>>=)return的类型构造函数。 Don't mix the values up with the types – this sort of terminological distinction helps keep things clearer! 不要将这些值与类型混合在一起 - 这种术语上的区别有助于使事情更清晰!

This structure looks a lot like the definition of >>= / >> for Monad Maybe . 这个结构看起来很像Monad Maybe的定义>>= / >>

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

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

so your original expression could be simplified to 所以你的原始表达可以简化为

ma >>= f >> ma

and this works for other monads. 这适用于其他monad。

However, I don't think this is actually what you want, as you can see ma occurring twice. 但是,我不认为这实际上是你想要的,因为你可以看到ma出现两次。 Instead, take the value from the first ma >>= bind, and carry it through to the end of the computation. 而是从第一个ma >>= bind获取值,并将其传递到计算结束。

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

or in do -notation do -notation

keepValue ma f = do
    a <- ma
    f a
    return a

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM