17

I have the following code but I feel it is too ugly and imperative. Would anybody rephrase it to be more functional? (I messed with MaybeT but could not make it work) Applicative answers welcome as well.

getString :: IO String pred :: String -> Bool f :: String -> String result :: IO (Maybe String) result = do s <- getString if pred s then return $ Just $ f s else return Nothing 

EDIT: A follow-up question: what if both pred and f also return results within IO (should I split this out into a separate question?)

getString :: IO String pred :: String -> IO Bool f :: String -> IO String result :: IO (Maybe String) result = do s <- getString b <- pred s if b then Just <$> f s else return Nothing 

5 Answers 5

29

I would begin by taking the logic here out of the IO monad. Your function can then be written as

result :: IO (Maybe String) result = foo <$> getString foo :: String -> Maybe String foo s | pred s = Just (f s) | otherwise = Nothing 

You could probably write foo in different ways using some fancy combinators, but I don't think that's necessary here. The most important thing is to get your logic out of IO so that it's easier to test.

Sign up to request clarification or add additional context in comments.

1 Comment

+1 It can't be denied that getting the logic out of IO is more important than having a fancy one-line solution.
10

Here's a nice little combinator:

ensure :: MonadPlus m => (a -> Bool) -> (a -> m a) ensure p x = guard (p x) >> return x 

Now we can write a pure function which checks your predicate and applies f when appropriate:

process :: String -> Maybe String process = fmap f . ensure pred 

Lifting this to an IO action is simply another fmap:

result = fmap process getString 

Personally, I'd probably inline process, and write that this way instead:

result = fmap (fmap f . ensure pred) getString 

...which is a relatively clean description of what's happening.

Comments

9

The obvious transformation from your code is to factor the return operations:

result = do s <- getString return $ if pred s then Just (f s) else Nothing 

This makes the pattern more apparent:

result = liftM g getString g s | pred s = Just (f s) | otherwise = Nothing 

By applying f from outside, we can make the next pattern apparent:

g s = liftM f $ if pred s then Just s else Nothing 

Which lets us remplace the if block:

g = liftM f . mfilter pred . return 

Summing it up:

result = liftM (liftM f . mfilter pred . return) getString 

Comments

4
import Control.Monad result = getString >>= (return . fmap f . (mfilter pred . Just) ) 

4 Comments

Notice that mfilter replaces your if-then-else.
result = (f <$> mfilter pred . Just) <$> getString
@FUZxxl that doesn't type check. mfilter pred . Just :: a -> Maybe a, but f <$> x is expecting x :: Maybe a. You need some sort of applicative composition operator f <.> g = \x -> f <$> g x, which if given lower precedence than . allows result = (f <.> mfilter pred . Just) <$> getString. Also, replacing Just with return allows any Monad to be used.
How about result = (fmap f . mfilter pred . return) <$> getString?
3

You can't easily get away with the clunky if-then-else, but you can get away with the redundant returns:

import Control.Monad result :: IO (Maybe String) result = go <$> getString where go s | pred s = Just $ f s | otherwise = Nothing 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.