2012-12-20 21 views
9

Tôi đang chuyển một ứng dụng Java sang Haskell. Phương pháp chính của ứng dụng Java theo mẫu:Làm thế nào để thực hiện lối ra/trả lại sớm trong Haskell?

public static void main(String [] args) 
{ 
    if (args.length == 0) 
    { 
    System.out.println("Invalid number of arguments."); 

    System.exit(1); 
    } 

    SomeDataType d = getData(arg[0]); 
    if (!dataOk(d)) 
    { 
    System.out.println("Could not read input data."); 

    System.exit(1); 
    } 

    SomeDataType r = processData(d); 
    if (!resultOk(r)) 
    { 
    System.out.println("Processing failed."); 

    System.exit(1); 
    } 

    ... 
} 

Vì vậy, tôi có các bước khác nhau và sau mỗi bước tôi có thể thoát ra với một mã lỗi, hoặc tiếp tục sang bước tiếp theo.

nỗ lực của tôi tại porting này để Haskell diễn ra như sau:

main :: IO() 
main = do 
     a <- getArgs 
     if (null args) 
      then do 
        putStrLn "Invalid number of arguments." 
        exitWith (ExitFailure 1) 
      else do 
        -- The rest of the main function goes here. 

Với giải pháp này, tôi sẽ có rất nhiều lồng nhau if-then-else (một cho mỗi điểm lối ra của mã Java bản gốc).

Có cách nào thanh lịch/thành ngữ hơn để triển khai mẫu này trong Haskell không? Nói chung, một cách thành ngữ Haskell để thực hiện một lối ra/trả lại sớm như được sử dụng trong một ngôn ngữ bắt buộc như Java là gì?

+1

Vui lòng đọc ví dụ về Đi bộ đường từ trang http://learnyouahaskell.com/a-fistful-of-monads. Nó cho ví dụ về Monad sử dụng Có lẽ datatype. Một khi kết quả của bất kỳ biểu thức là Không có gì, kết quả của tất cả các biểu thức sau là Không có gì, tốt như bạn đã thoát tại điểm thất bại. –

+0

@ManojR - Có lẽ không thực sự phù hợp ở đây vì bạn cũng muốn lý do cho sự thất bại. – Lee

+0

Cách tiếp cận cổ điển là tách quy trình thành chức năng xử lý "tin cậy" (giả định các tham số chính xác) và chức năng kiểm tra độ sáng "hoang tưởng" (chỉ kiểm tra xem thông số có chính xác không) ... –

Trả lời

6

Một cách tiếp cận hơi nhạy cảm hơn trong Haskell có sử dụng cùng một loại logic có điều kiện bạn đã cố gắng có thể trông như thế này:

fallOverAndDie :: String -> IO a 
fallOverAndDie err = do putStrLn err 
         exitWith (ExitFailure 1) 

main :: IO() 
main = do a <- getArgs 
      case a of 
       [d] | dataOk d -> doStuff $ processData d 
        | otherwise -> fallOverAndDie "Could not read input data." 
       _ -> fallOverAndDie "Invalid number of arguments." 


processData r 
    | not (resultOk r) = fallOverAndDie "Processing failed." 
    | otherwise  = do -- and so on... 

Trong trường hợp đặc biệt này, cho rằng exitWith chấm dứt chương trình nào, chúng ta có thể cũng phân phối hoàn toàn với các điều kiện lồng nhau:

main :: IO() 
main = do a <- getArgs 
      d <- case a of 
        [x] -> return x 
        _ -> fallOverAndDie "Invalid number of arguments." 
      when (not $ dataOk d) $ fallOverAndDie "Could not read input data." 
      let r = processData d 
      when (not $ resultOk r) $ fallOverAndDie "Processing failed." 

Sử dụng cùng một fallOverAndDie như trước đây. Đây là bản dịch trực tiếp hơn nhiều của Java gốc.

Trong trường hợp chung, ví dụ Monad cho Either cho phép bạn viết một cái gì đó rất giống với ví dụ sau ở trên trong mã thuần túy. Bắt đầu từ việc này thay thế:

fallOverAndDie :: String -> Either String a 
fallOverAndDie = Left 

notMain x = do a <- getArgsSomehow x 
       d <- case a of 
         -- etc. etc. 

... phần còn lại của mã không thay đổi so với ví dụ thứ hai của tôi.Tất nhiên bạn có thể sử dụng một cái gì đó khác hơn là chỉ String; để tạo lại phiên bản IO một cách trung thực hơn, bạn có thể sử dụng Either (String, ExitCode) để thay thế.

Bên cạnh đó, việc sử dụng này Either không giới hạn lỗi xử lý - nếu bạn có một số tính toán phức tạp trở lại một Double, sử dụng Either Double Double và phong cách monadic tương tự như trên, bạn có thể sử dụng Left để giải cứu sớm với một giá trị trả về , sau đó bọc chức năng bằng cách sử dụng một cái gì đó như either id id để thu gọn hai kết quả và nhận được một đơn Double.

3

Một cách là sử dụng biến áp đơn lẻ ErrorT. Với nó, bạn có thể đối xử với nó như một đơn nguyên thường xuyên, trả lại, ràng buộc, tất cả những thứ tốt đó, nhưng bạn cũng có chức năng này, throwError. Điều này làm cho bạn bỏ qua các phép tính sau đây cho đến khi bạn đạt đến cuối tính toán đơn điệu, hoặc khi bạn gọi hàm catchError. Điều này là để xử lý lỗi mặc dù, nó không có nghĩa là để được tự ý thoát khỏi một chức năng trong Haskell. Tôi đề nghị nó bởi vì nó có vẻ như đó là những gì bạn đang làm.

Một ví dụ nhanh:

import Control.Monad.Error 
import System.Environment 

data IOErr = InvalidArgs String | GenErr String deriving (Show) 
instance Error IOErr where 
    strMsg = GenErr --Called when fail is called 
    noMsg = GenErr "Error!" 
type IOThrowsError = ErrorT IOErr IO 

process :: IOThrowsError [String] 
process = do 
    a <- liftIO getArgs 
    if length a == 0 
    then throwError $ InvalidArgs "Expected Arguments, received none" 
    else return a 

main = do 
    result <- runErrorT errableCode 
    case result of 
    Right a -> putStrLn $ show a 
    Left e -> putStrLn $ show e 
    where errableCode = do 
    a <- process 
    useArgs a 

bây giờ nếu quá trình ném một lỗi, useArgs sẽ không được thực thi.

0

Đây là những gì tôi đã nảy ra

data ExtendedMaybe a = Just a | GenErr String 

isWrongArgs :: [string] -> ExtendedMaybe [string] 
isWrongArgs p = if (length p == 0) 
then GenErr "Invalid number of arguments" 
else p 

getData :: ExtendedMaybe [string] -> ExtendedMaybe sometype 
getData GenErr = GenErr 
getData [string] = if anything wrong return GenErr "could not read input data" 

processdata :: ExtendedMaybe sometype -> ExtendedMaybe sometype 
processdata GenErr = GenErr 

main = do 
    a <- getArgs 
    d <- isWrongArgs a 
    r <- getData d 
    f <- processdata r 

Gần ý tưởng là bạn có một kiểu dữ liệu như lẽ một, chỉ thay vì Không có gì quý vị có GenErr String, mà bạn xác định trong mọi chức năng mà xử lý dữ liệu. Nếu kiểu dữ liệu đầu vào là GenErr, hãy trả về nó. Nếu không, hãy kiểm tra lỗi trong dữ liệu và trả về GenErr với chuỗi thích hợp. Đây có thể không phải là cách hoàn hảo, nhưng vẫn là một cách. Điều này không thoát ra tại điểm chính xác của lỗi, nhưng đảm bảo rằng không có nhiều xảy ra sau khi xảy ra lỗi.

+8

Loại 'ExtendedMaybe' của bạn về cơ bản giống với' Chuỗi ký tự ' – Lee

Các vấn đề liên quan