[英]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
; 首先,一些小事:因为
Applicative
是Monad
的超类,我更喜欢pure
的return
; 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 =<< ma
, ma >>= 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 b
或do ma ; pure b
do ma ; pure b
with b <$ ma
. do ma ; pure b
, b <$ 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.