Go through it!
keepValue :: Monad m => ma -> (a -> mb) -> ma keepValue ma f = _
So what do we need to do keepValue ? Well, the first thing we need to do is use ma , so we can connect it to f .
keepValue :: Monad m => ma -> (a -> mb) -> ma keepValue ma f = do a <- ma _
Now we have a value va type a , so we can pass it to f .
keepValue :: Monad m => ma -> (a -> mb) -> ma keepValue ma f = do va <- ma vb <- f va _
And finally, we want to create va , so we can just do this:
keepValue :: Monad m => ma -> (a -> mb) -> ma keepValue ma f = do va <- ma vb <- f va return va
This is how I could write the first draft of any monadic function like this. Then I would clean it. First, some little things: since Applicative is a Monad superclass, I prefer pure - return ; we did not use vb ; and I would drop v in the names. So for the do based version of this function, I think the best option
keepValue :: Monad m => ma -> (a -> mb) -> ma keepValue ma f = do a <- ma _ <- fa pure a
Now, however, we can begin to make the implementation better. First, we can replace _ <- f va with an explicit call (>>) :
keepValue :: Monad m => ma -> (a -> mb) -> ma keepValue ma f = do a <- ma fa >> pure a
And now we can apply simplification. You may know that we can always replace (>>=) plus pure / return with fmap / (<$>) : any of pure . f =<< ma pure . f =<< ma , ma >>= pure . f ma >>= pure . f or do a <- ma ; pure $ fa do a <- ma ; pure $ fa (all of which are equivalent) can replace f <$> ma . However, a class of type Functor has another, less well-known method, (<$) :
(<$) :: a -> fb -> fa
Replace all input locations with the same value. The default definition is fmap . const fmap . const , but this can be overridden by a more efficient version.
So, we have a similar replacement rule for (<$) : we can always replace ma >> pure b or do ma ; pure b do ma ; pure b on b <$ ma . It gives us
keepValue :: Monad m => ma -> (a -> mb) -> ma keepValue ma f = do a <- ma a <$ fa
And I think this is the shortest reasonable version of this feature! There are no good accurate tricks to make it cleaner; one indicator is the multiple use of a in the second line of the do block.
By the way, a terminological note: you perform two monadic actions or two monadic values; you do not work * "two monads". A monad is something like Maybe - a type constructor that supports (>>=) and return . Do not mix values โโwith types - this kind of terminological distinction helps to keep things clearer!