2014-04-11 18 views
5

Tôi đang cố gắng viết một chương trình đơn giản trong Haskell. Về cơ bản nó sẽ chạy hai lệnh shell song song. Đây là mã:Logical and strictness with IO monad

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

Nhưng lệnh "foo" trả về ExitFailure và tôi mong "thanh" không bao giờ chạy. Đây không phải là trường hợp! Cả hai đều chạy và cả hai đều hiển thị lỗi trên bảng điều khiển.

Đồng thời

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

đánh giá hoàn toàn tốt; điều này có nghĩa là đối số thứ hai không được tính toán. Làm cách nào để thực hiện tương tự với các lệnh hệ thống trong ứng dụng của tôi?

+2

Xem định nghĩa của 'liftM2'. Bạn cần một phiên bản đơn thuần của '(&&)' có độ nghiêm ngặt (monadic) mà bạn muốn. – augustss

+4

Bạn nói rằng bạn muốn chạy các lệnh song song và chạy "bar" chỉ khi "foo" thành công. Không có nghĩa lý gì. Quyết định, bạn muốn nó song song hoặc tuần tự? – remdezx

Trả lời

5

Tôi nghĩ rằng sử dụng && để thực hiện có điều kiện là một thói quen xấu. Chắc chắn nó chỉ là một vấn đề lý do để làm điều này cho tác dụng phụ miễn phí các công cụ như False && all (/=0) [1..], nhưng khi có những tác dụng phụ nó khá lộn xộn để làm cho họ phụ thuộc vào một cách ẩn như vậy. (Vì thực hành quá phổ biến, hầu hết các lập trình viên sẽ ngay lập tức nhận ra nó, nhưng tôi không nghĩ đó là điều chúng ta nên khuyến khích, ít nhất không phải trong Haskell.)

Những gì bạn muốn là một cách để diễn đạt: "thực hiện một số hành động, cho đến một sản lượng False ".

Ví dụ đơn giản của bạn, tôi chỉ muốn làm điều đó một cách rõ ràng:

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

hay ngắn: run "foo" >>= (`when` run "bar").

Nếu bạn muốn sử dụng rộng rãi hơn, bạn nên làm điều đó một cách tổng quát hơn. Đơn giản chỉ cần kiểm tra một điều kiện boolean không phải là rất chung chung, bạn sẽ thường cũng muốn vượt qua trên một số loại kết quả. Thông qua kết quả là lý do chính chúng tôi sử dụng một đơn nguyên cho IO, thay vì chỉ đơn giản là danh sách các hành động nguyên thủy.

Aha, monads! Thật vậy, những gì bạn cần là IO monad, nhưng với một "kill switch" bổ sung: hoặc bạn thực hiện một chuỗi các hành động, có thể với một số kết quả để vượt qua, hoặc - nếu bất kỳ lỗi nào trong số đó thất bại - bạn hủy bỏ toàn bộ điều. Nghe có vẻ giống như Maybe, phải không?

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

Bạn nói rằng bạn muốn chạy các lệnh song song và chạy "bar" chỉ khi "foo" thành công. Không có nghĩa lý gì. Bạn phải quyết định, nếu bạn muốn chạy nó song song hoặc tuần tự.

Nếu bạn muốn chạy "bar" chỉ khi "foo" thành công, hãy thử:

import System.Process 

main = do callCommand "foo" 
      callCommand "bar" 

Hoặc nếu bạn muốn chạy nó song song, hãy thử:

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 
1

Hành động IO

liftM2 f action1 action2 

chạy cả hai hành động cho bất kỳ nhị phân chức năng f là (ví dụ, (&&) trong trường hợp của bạn).Nếu bạn muốn chỉ cần chạy action1 bạn có thể mã nó như sau:

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

Sử dụng nó như sAnd action1 action2.