main :: IO ()
main =
putStrLn =<< cachedOrFetched
<$> getLine
<*> getLine
cachedOrFetched :: String -> String -> String
cachedOrFetched cached fetched =
if not $ null cached
then cached
else fetched
The above code performs IO for twice. But the desired behavior is to skip the second IO when the result of the first IO is not null.
Here is the entire thing
I started learning Haskell about two weeks ago. I am not expecting a job from it, but simply attracted by programming language itself. Because it's the "purest" as far as I know.
At first, everything seemed as good as I expected.
But then I found I have to write IO
s among my pure code.
I spent quite a long time to find a way to do impure-taint-control.
Applicative Functor seemed to be the rescue.
With it, I can "curry" the impure IOs into my pure functions,
which saves a lot of do
, <-
and explicit IO
notations.
However, I got stuck with this very problem -
I am not able to skip unnecessary IOs in pure functions.
Again, a lot of searches and read. No satisfying answer.
I know I can achieve this by using do
or when
.
Given using too many do
s violates my original intention using Haskell,
I probably gonna live with when
.
Or is there a better way? A purer way?
CodePudding user response:
To skip IO, you must tell the cachedOrFetched
that you're doing IO, instead of passing two strings to it after reading both lines. Only then, it can conditionally run the second getLine
IO action. Of course, cachedOrFetched
doesn't necessarily need to deal with IO
, it can work for any monad:
main :: IO ()
main = putStrLn =<< cachedOrFetched getLine getLine
cachedOrFetched :: Monad m => m String -> m String -> m String
cachedOrFetched first second = do
cached <- first
if not $ null cached
then return cached
else second
I would probably write
main :: IO ()
main = getLine >>= ifNullDo getLine >>= putStrLn
ifNullDo :: Applicative f => f String -> String -> f String
ifNullDo fetch cached =
if not $ null cached
then pure cached
else fetch
CodePudding user response:
This is what your code looks like when written in do
notation:
main :: IO ()
main = do
cached <- getLine
fetched <- getLine
putStrLn $ cachedOrFetched cached fetched
You can see that both IO actions are getting done before the call to cachedOrFetched
is even happening. You could do everything in main
like so:
main :: IO ()
main = do
cached <- getLine
if not $ null cached
then putStrLn cached
else do
fetched <- getLine
putStrLn fetched
The reason is because cachedOrFetched
must have access to both values (they are arguments to its call!) before being able to make a decision about which to choose.