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

Gio*_*gio 9 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);
  }

  ...
}
Run Code Online (Sandbox Code Playgroud)

所以我有不同的步骤,每个步骤后我都可以退出错误代码,或继续执行以下步骤.

我将此移植到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.
Run Code Online (Sandbox Code Playgroud)

有了这个解决方案,我将有许多嵌套if-then-else(原始Java代码的每个出口点一个).

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

C. *_*ann 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...
Run Code Online (Sandbox Code Playgroud)

在这种特殊情况下,无论如何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."
Run Code Online (Sandbox Code Playgroud)

使用与fallOverAndDie以前相同.这是原始Java的更直接的翻译.

在一般情况下,Monad实例Either允许您在纯代码中编写与上一个示例非常相似的内容.从此开始:

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

notMain x = do a <- getArgsSomehow x
               d <- case a of
                        -- etc. etc.
Run Code Online (Sandbox Code Playgroud)

...其余的代码与我的第二个例子没有变化.你当然可以使用其他东西String; 为了更忠实地重新创建IO版本,您可以使用Either (String, ExitCode).

另外,这种用法Either不仅限于错误处理 - 如果你有一些复杂的计算返回a Double,使用Either Double Double和上面相同的monadic样式,你可以使用Left返回值提前纾困,然后使用像either id id崩溃这两个结果并得到一个单一的结果Double.


Dan*_*zer 5

一种方法是使用ErrorTmonad 转换器。有了它,你可以把它当作一个普通的 monad,返回,绑定,所有这些好东西,但你也得到了这个函数,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
Run Code Online (Sandbox Code Playgroud)

现在,如果进程抛出错误,则不会执行 useArgs。