Логичность И строгость с монадой IO

Я пытаюсь написать простую программу на Хаскеле. Он должен в основном запускать две команды оболочки параллельно. Вот код:

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")

Но команда "foo" возвращает ExitFailure, и я ожидаю, что "bar" никогда не запустится. Это не вариант! Они оба работают, и оба показывают ошибки на консоли.

В то же время

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

оценивает на отлично; это означает, что второй аргумент не рассчитывается. Как мне сделать то же самое с системными командами в моем приложении?

3 ответа

Решение

Я думаю, используя && условное исполнение - это что-то плохое. Конечно, это просто повод сделать это для вещей без побочных эффектов, таких как False && all (/=0) [1..], но когда есть побочные эффекты, довольно сложно сделать их зависимыми таким скрытым способом. (Поскольку эта практика настолько распространена, большинство программистов сразу ее узнают; но я не думаю, что мы должны поощрять это, по крайней мере, в Haskell.)

То, что вы хотите, это способ выразить: "выполнить некоторые действия, пока один не даст False".

Для вашего простого примера я бы просто сделал это явно:

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

или коротко: run "foo" >>= (`when` run "bar"),

Если вы хотите использовать это более широко, лучше сделать это более общим способом. Простая проверка логического условия не является очень общей, обычно вы также хотите передать какой-то результат. Передача результатов является основной причиной, по которой мы используем монаду для ввода-вывода, а не просто списки примитивных действий.

Ага, монады! В самом деле, вам нужна монада ввода-вывода, но с дополнительным "переключателем уничтожения": либо вы выполняете последовательность действий, каждое из которых, возможно, дает какой-то результат, либо - если какое-либо из них не удается - вы прерываете все это. Звучит очень похоже Maybe, право?

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"

Вы говорите, что хотите запускать команды параллельно и запускать "bar" только в случае успеха "foo". Это не имеет никакого смысла. Вы должны решить, хотите ли вы запустить его параллельно или последовательно.

Если вы хотите запустить "bar" только в случае успеха "foo", попробуйте:

import System.Process

main = do callCommand "foo"
          callCommand "bar"

Или, если вы хотите запустить его параллельно, попробуйте:

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

Действие IO

liftM2 f action1 action2

выполняет оба действия для любой двоичной функции f есть (например, (&&) в твоем случае). Если вы хотите просто запустить action1 Вы можете кодировать его следующим образом:

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

Используйте это как sAnd action1 action2,

Другие вопросы по тегам