问题
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?
回答1:
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"
回答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
回答3:
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
.
来源:https://stackoverflow.com/questions/23012793/logical-and-strictness-with-io-monad