Logical AND strictness with IO monad
Asked Answered
E

3

5

I am trying to write a simple program in Haskell. It should basically run two shell commands in parallel. Here is the code:

import System.Cmd
import System.Exit
import Control.Monad

exitCodeToBool ExitSuccess = True
exitCodeToBool (ExitFailure _) = False

run :: String -> IO Bool
run = (fmap exitCodeToBool) . system

main = liftM2 (&&) (run "foo") (run "bar")

But command "foo" returns ExitFailure and I expect "bar" never to run. This is not the case! They both run and both show errors on the console.

At the same time

False && (all (/= 0) [1..])

evaluates perfectly well; this means the second argument is not calculated. How do I do the same with system commands in my app?

Eudemonism answered 11/4, 2014 at 12:38 Comment(2)
Look at the definition of liftM2. You need a monadic version of (&&) that has the (monadic) strictness you want.Obscenity
You say that you want to run commands in parallel and run "bar" only if "foo" succeeds. It makes no sense. Decide, you want it parallel or sequential?Godunov
R
5

I think using && for conditional execution is something of a bad habit. Sure it's just a matter of reason to do this for side-effect-free stuff like the False && all (/=0) [1..], but when there are side-effects it's quite confusionsome to make them dependent in such a hidden way. (Because the practise is so widespread, most programmers will immediately recognise it; but I don't think it's something we should encourage, at least not in Haskell.)

What you want is a way to express: "execute some actions, until one yields False".

For your simple example, I'd just do it explicitly:

main = do
   e0 <- run "foo"
   when e0 $ run "bar"

or short: run "foo" >>= (`when` run "bar").

If you want to use this more extensively, it's good to do it in a more general manner. Simply checking a boolean condition is not very general, you'll normally also want to pass on some kind of result. Passing on results is the main reason we use a monad for IO, rather then simply lists of primitive actions.

Aha, monads! Indeed, what you need is the IO monad, but with an extra "kill switch": either you do a sequence of actions, each possibly with some result to pass on, or – if any of them fails – you abort the entire thing. Sounds a lot like Maybe, right?

http://www.haskell.org/hoogle/?hoogle=MaybeT

import Control.Monad.Trans.Maybe

run :: String -> MaybeT IO ()
run s = MaybeT $ do
   e <- system s
   return $ if exitCodeToBool e then Just () else Nothing

main = runMaybeT $ do
   run "foo"
   run "bar"
Roundelay answered 11/4, 2014 at 13:40 Comment(0)
G
2

You say that you want to run commands in parallel and run "bar" only if "foo" succeeds. It makes no sense. You have to decide, if you want to run it in parallel or sequential.

If you want run "bar" only if "foo" succeed, try:

import System.Process

main = do callCommand "foo"
          callCommand "bar"

Or if you want to run it in parallel, try:

import System.Process
import Control.Concurrent


myForkIO :: IO () -> IO (MVar ())
myForkIO io = do
    mvar <- newEmptyMVar
    forkFinally io (\_ -> putMVar mvar ())
    return mvar

main = do mvar <- myForkIO $ callCommand "foo"
          callCommand "bar"
          takeMVar mvar
Godunov answered 11/4, 2014 at 13:36 Comment(0)
S
1

The IO action

liftM2 f action1 action2

runs both actions for any binary function f is (e.g., (&&) in your case). If you want to just run action1 you can code it as follows:

--| Short-circuit &&
sAnd :: IO Bool -> IO Bool -> IO Bool
sAnd action1 action2 = do
    b <- action1
    if b then action2 else return False

Use it as sAnd action1 action2.

Stogner answered 11/4, 2014 at 22:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.