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
Now that we also know that IO is a monad, we can wrap up the discussion we started there.
The I/O monad contains primitives which build composite actions, a process similar to joining statements in sequential order using `;' in other languages. Thus the monad serves as the glue which binds together the actions in a program.
IO is the way how Haskell differentiates between code that is referentially transparent and code that is not. IO a is the type of an IO action that returns an a . You can think of an IO action as a piece of code with some effect on the real world that waits to get executed.
Haskell is a pure language Moreover, Haskell functions can't have side effects, which means that they can't effect any changes to the "real world", like changing files, writing to the screen, printing, sending data over the network, and so on.
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.
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.
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