Skirmantas Kligys
Skirmantas Kligys

Reputation: 826

IO and Maybe monad interaction

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

Answers (5)

Daniel Wagner
Daniel Wagner

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

JB.
JB.

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

leftaroundabout
leftaroundabout

Reputation: 120741

import Control.Monad

result = getString >>= (return . fmap f . (mfilter pred . Just) )

Upvotes: 4

fuz
fuz

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

hammar
hammar

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

Related Questions