使用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吗?
它看起来有点内置>>
,但我在标准库中找不到任何内容。
答案 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函数的初稿。然后,我要把它清理干净。首先,一些小事:由于Applicative
是Monad
的超类,我更喜欢pure
到return
;我们没有使用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