如何在Haskell中实现提前退出/返回?

时间:2012-12-20 10:55:59

标签: java haskell control-structure

我正在将一个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代码的每个出口点都有一个)。

在Haskell中实现这种模式有更优雅/惯用的方法吗?一般来说,什么是实现早期退出/返回的Haskell惯用方法,如Java等命令式语言中所使用的那样?

3 个答案:

答案 0 :(得分:6)

在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的{​​{1}}实例允许您在纯代码中编写与上面示例非常相似的内容。从此开始:

Either

...其余代码与我的第二个示例相同。你当然可以使用fallOverAndDie :: String -> Either String a fallOverAndDie = Left notMain x = do a <- getArgsSomehow x d <- case a of -- etc. etc. 以外的东西;为了更忠实地重新创建String版本,您可以使用IO代替。

此外,Either (String, ExitCode)的使用并不仅限于错误处理 - 如果您有一些复杂的计算返回Either,使用Double并使用与上述相同的monadic样式,可以使用Either Double Double尽早使用返回值进行挽救,然后使用Left之类的内容包装该函数以折叠这两个结果并获得单个either id id

答案 1 :(得分:3)

一种方法是使用ErrorT monad变换器。有了它,你可以把它当作常规monad,return,bind,所有好东西,但你也得到这个函数,throwError。这会导致您跳过以下计算,直到您到达monadic计算结束,或者调用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。

答案 2 :(得分: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

大概这个想法是你有一个像Maybe a这样的数据类型,而不是Nothing你有GenErr String,你在每个处理数据的函数中定义它。如果输入数据类型是GenErr,则只返回。否则检查数据中的错误并使用适当的字符串返回GenErr。这可能不是完美的方式,但仍然是一种方式。这不会在错误的确切位置退出,但保证在发生错误后不会发生太多事情。