10

JavaアプリケーションをHaskellに移植しています。Javaアプリケーションの主なメソッドは、次のパターンに従います。

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);
  }

  ...
}

したがって、さまざまな手順があり、各手順の後にエラーコードを表示して終了するか、次の手順に進むことができます。

これをHaskellに移植しようとした私の試みは次のとおりです。

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.

このソリューションでは、ネストされたものがたくさんありますif-then-else(元のJavaコードの出口点ごとに1つ)。

Haskellでこのパターンを実装するためのよりエレガントで慣用的な方法はありますか?一般に、Javaのような命令型言語で使用されるような早期終了/復帰を実装するHaskellの慣用的な方法は何ですか?

4

3 に答える 3

7

あなたが試したのと同じ種類の条件付きロジックを使用するHaskellのもう少し賢明なアプローチは、次のようになります。

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...

この特定のケースでは、とにかくexitWithプログラムを終了することを考えると、ネストされた条件を完全に省くこともできます。

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

以前と同じfallOverAndDieように使用します。これは、元のJavaをはるかに直接翻訳したものです。

一般的なケースでは、のMonadインスタンスをEither使用すると、上記の後者の例と非常によく似たものを純粋なコードで記述できます。代わりにこれから始めます:

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

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

...残りのコードは私の2番目の例から変更されていません。もちろん、それ以外のものを使用することもできStringます。バージョンをより忠実に再作成するには、代わりにIO使用できますEither (String, ExitCode)

さらに、このの使用はエラー処理に限定されません-上記と同じモナディックスタイルを使用してをEither返す複雑な計算がある場合は、戻り値で早期にベイルアウトしてから、次のようなものを使用して関数をラップするために使用できます2つの結果を折りたたんで、1つを取得します。DoubleEither Double DoubleLefteither id idDouble

于 2012-12-20T17:58:07.890 に答える
5

1つの方法は、ErrorTモナド変換子を使用することです。これを使用すると、通常のモナド、リターン、バインド、その他すべての優れた機能のように扱うことができますが、この関数も取得できますthrowError。これにより、モナディック計算の最後に到達するまで、またはcatchErrorを呼び出すときに、次の計算をスキップします。これはエラー処理のためのものですが、Haskellの関数を任意に終了するためのものではありません。それがあなたがしていることのように思われるので、私はそれを提案しました。

簡単な例:

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

プロセスがエラーをスローした場合、useArgsは実行されません。

于 2012-12-20T11:23:10.723 に答える
0

これは私が思いついたものです

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

大まかに言うと、データを処理するすべての関数で定義するGenErr文字列がないのではなく、たぶんaのようなデータ型があるということです。入力データ型がGenErrの場合は、単にそれを返します。それ以外の場合は、データのエラーを確認し、適切な文字列でGenErrを返します。これは完璧な方法ではないかもしれませんが、それでも1つの方法です。これはエラーの正確なポイントで終了するわけではありませんが、エラーが発生した後に多くのことが発生しないことを保証します。

于 2012-12-20T14:04:42.787 に答える