Haskell: run two monads, save the result of the first

Playing with Haskell, and now I'm trying to create a function like

keepValue :: (Monad m) => ma -> (a -> mb) -> ma 

with the following semantics: he must apply the value of the monad to a function that returns the second monad, and save the result of the first monad, but the effect of the second

I have a working function in the 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 does not start (so there is no second side effect), but if the first value is Just , then the function starts (with a side effect). I retain the effect of the second calculation (for example, Nothing makes the whole expression Nothing ), but the original value.

Now interesting. Can it work for any monad?

It looks like built-in >> , but I did not find anything in the standard library.

+5
source share
3 answers

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!

+11
source

This structure is very similar to the definition >>= / >> for Monad Maybe .

 case foo of Nothing -> Nothing Just _ -> bar foo >>= \_ -> bar foo >> bar 

so that your original expression can be simplified to

 ma >>= f >> ma 

and it works for other monads.

However, I do not think that this is actually what you want, since you see that ma happens twice. Instead, take the value from the first binding ma >>= and transfer it to the end of the calculation.

 keepValue ma f = ma >>= \a -> fa >> return a 

or in do notation

 keepValue ma f = do a <- ma fa return a 
+1
source

You can define:

 passThrough f = (>>) <$> f <*> pure 

and then instead

 keepValue ma f 

write down

 ma >>= passThrough f 

Then read the line and print it twice (say) will be

 getLine >>= passThrough putStrLn >>= putStrLn 
0
source

Source: https://habr.com/ru/post/1264225/


All Articles