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