Логичность И строгость с монадой 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
,