Logical AND strictness with IO monad

一世执手 提交于 2020-01-02 04:51:10

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!