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

dmz*_*rsk 5 monads haskell

和Haskell一起玩,现在我尝试创建一个类似的函数

keepValue :: (Monad m) => m a -> (a -> m b) -> m a
Run Code Online (Sandbox Code Playgroud)

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

我有Maybemonad 的工作函数:

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

keepValue ma f = case ma >>= f of
    Nothing -> Nothing
    Just _ -> ma
Run Code Online (Sandbox Code Playgroud)

因此,如果第一个值是Nothing,则不运行该函数(因此没有第二个副作用),但如果第一个值是Just,则运行该函数(带副作用).我保持第二次计算的效果(例如,Nothing使整个表达式Nothing),但原始值.

现在我好奇.它适用于任何monad吗?

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

Ant*_*sky 11

让我们来看看吧!

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = _
Run Code Online (Sandbox Code Playgroud)

那么我们想做keepValue什么?嗯,我们应该做的第一件事是使用ma,所以我们可以将它连接到f.

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  a <- ma
  _
Run Code Online (Sandbox Code Playgroud)

现在我们有一个va类型的值a,所以我们可以传递给它f.

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  va <- ma
  vb <- f va
  _
Run Code Online (Sandbox Code Playgroud)

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

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  va <- ma
  vb <- f va
  return va
Run Code Online (Sandbox Code Playgroud)

这就是我如何编写像这样的任何monadic函数的初稿.然后,我会把它清理干净.首先,一些小东西:既然Applicative是一个超类Monad,我更喜欢purereturn; 我们没有用vb; 我会放弃v名字.所以对于do这个函数的基于注释的版本,我认为最好的选择是

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  a <- ma
  _ <- f a
  pure a
Run Code Online (Sandbox Code Playgroud)

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

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  a <- ma
  f a >> pure a
Run Code Online (Sandbox Code Playgroud)

现在,我们可以应用简化.你可能知道,我们可随时更换(>>=)pure/ returnfmap/ (<$>):任何的pure . f =<< ma,ma >>= pure . fdo a <- ma ; pure $ f a(所有这些是等同的)可以被替换f <$> ma.但是,Functor类型类还有另一种不太为人所知的方法(<$):

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

所以我们有一个类似的替换规则(<$):我们可以随时替换ma >> pure bdo ma ; pure b使用b <$ ma.这给了我们

keepValue :: Monad m => m a -> (a -> m b) -> m a
keepValue ma f = do
  a <- ma
  a <$ f a
Run Code Online (Sandbox Code Playgroud)

而且我认为这是此功能的最短合理版本!没有任何好的无点技巧可以让它变得更干净; 一个指标是ado块的第二行多次使用.


顺便提一下,术语说明:你正在运行两个monadic动作,或两个monadic值 ; 你没有运行*"两个单子".monad就像Maybe- 一个支持(>>=)和的类型构造函数return.不要将这些值与类型混合在一起 - 这种术语上的区别有助于使事情更清晰!

  • 你的意思是`import Control.Arrow; keepValue ma f = ma >> = uncurry(>>).(f &&& return)`不算是一个好方法吗?;-P (3认同)
  • @ephemient:我更喜欢`(.((<$)<*>)).(>> =)`,个人:-)或者`ma >> =(<$)<*> f`,如果你真的喜欢变量名! (3认同)
  • 这些甚至是perl程序员因为我们羡慕我们的哈克勒斯而变得苍白的时候,因为当我们的猫走过键盘时,它是经过类型检查的 (3认同)