Reputation: 826
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
Upvotes: 17
Views: 10281
Reputation: 153172
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.
Upvotes: 10
Reputation: 42154
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
Upvotes: 9
Reputation: 120741
import Control.Monad
result = getString >>= (return . fmap f . (mfilter pred . Just) )
Upvotes: 4
Reputation: 93127
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
Upvotes: 3
Reputation: 139910
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.
Upvotes: 29