Playing with Haskell and now I try to create a function like
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
I have a working function in case of 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). I keep effect of the second computation (e.g., Nothing
makes the whole expression Nothing
), but the original value.
Now I wonder. Can it work for any 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? Well, the first thing we should do is use ma
, so we can connect it to 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
.
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:
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. Then, I'd clean it up. First, some small things: since Applicative
is a superclass of Monad
, I prefer pure
to return
; we didn't use vb
; and I'd drop the v
in the names. So for a do
-notation based version of this function, I think the best option is
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 (>>)
:
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
, ma >>= pure . f
, or do a <- ma ; pure $ f a
(all of which are equivalent) can be replaced by f <$> ma
. However, the Functor
type class has another, less-well-known method, (<$)
:
(<$) :: a -> f b -> f a
Replace all locations in the input with the same value. The default definition isfmap . const
, but this may be overridden with a more efficient version.
So we have a similar replacement rule for (<$)
: we can always replace ma >> pure b
or do ma ; pure b
with 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.
Incidentally, a terminology note: you're running two monadic actions, or two monadic values; you're not running *"two monads". A monad is something like Maybe
– a type constructor which supports (>>=)
and 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
.
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.
However, I don't think this is actually what you want, as you can see ma
occurring twice. Instead, take the value from the first ma >>=
bind, and carry it through to the end of the computation.
keepValue ma f =
ma >>= \a ->
f a >>
return a
or in do
-notation
keepValue ma f = do
a <- ma
f a
return a
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With