如何从Haskell中的read函数中捕获无解析异常?

时间:2011-02-25 18:54:31

标签: haskell exception-handling io

在我的Haskell程序中,我想读取用户使用getLine函数给出的值。然后,我想使用read函数将此值从字符串转换为适当的Haskell类型。如何捕获read函数抛出的解析错误并要求用户重新输入值?

我是否正确地认为这不是“IO错误”,因为它不是由于IO系统无法正常运行而导致的错误?这是一个语义错误,所以我不能使用IO错误处理机制?

4 个答案:

答案 0 :(得分:27)

你不想。您想要使用reads,可能就是这样:

maybeRead = fmap fst . listToMaybe . reads

(尽管你可能想要在元组的第二个元素不是""时出错,也就是说,如果还有一个剩余的字符串)

原因为什么要使用读取而不是捕获error异常,纯代码中的异常是邪恶的,因为非常很容易尝试抓住他们在错误的地方:注意他们只有在被迫时才会飞,而不是之前。找到那里可能是一个非平凡的练习。这是(原因之一)为什么Haskell程序员喜欢保持他们的代码总数,即终止和无异常。

您可能希望查看正确的解析框架(例如parsec)和haskeline

答案 1 :(得分:8)

这是@ barsoap答案的补遗。

Haskell异常可能被抛出,包括纯代码,但它们只能从IO monad中捕获。为了捕获纯代码抛出的异常,您需要在IO语句中使用catchtry来强制对纯代码进行求值。

str2Int :: String -> Int -- shortcut so I don't need to add type annotations everywhere
str2Int = read

main = do
  print (str2Int "3") -- ok
  -- print (str2Int "a") -- raises exception
  eVal <- try (print (str2Int "a")) :: IO (Either SomeException ())
  case eVal of
    Left e -> do -- couldn't parse input, try again
    Right n -> do -- could parse the number, go ahead

你应该使用比SomeException更具体的东西,因为它会抓住任何东西。在上面的代码中,如果try无法解析字符串,Left exception将返回read,但如果尝试时出现IO错误,它也会返回Left exception打印值,或任何可能出错的其他内容(内存不足等)。

现在,这就是纯代码中的异常是邪恶的原因。如果IO代码实际上没有强制评估结果怎么办?

main2 = do
  inputStr <- getLine
  let data = [0,1,read inputStr] :: [Int]
  eVal <- try (print (head data)) :: IO (Either SomeException ())
  case eVal of
    Right () -> do -- No exception thrown, so the user entered a number ?!
    Left e   -> do -- got an exception, probably couldn't read user input

如果你运行它,你会发现无论用户输入什么,你总是会在case语句的Right分支中结束。这是因为传递给try的IO操作不会尝试read输入的字符串。它打印列表data的第一个值,它是常量,永远不会触及列表的尾部。因此,在case语句的第一个分支中,编码器认为数据已经过评估但不是,read可能仍会抛出异常。

read用于反序列化数据,而不是解析用户输入的输入。使用reads,或切换到真正的解析器组合库。我喜欢uu-parsinglib,但parsecpolyparse和其他许多人也很好。不管怎样,你很可能不久就需要额外的电力。

答案 2 :(得分:7)

有readMaybe和readEither满足您的期望。您可以在Text.Read包中找到此函数。

答案 3 :(得分:3)

这是一个改进的maybeRead,只允许尾随空格,但没有别的:

import Data.Maybe
import Data.Char

maybeRead2 :: Read a => String -> Maybe a
maybeRead2 = fmap fst . listToMaybe . filter (null . dropWhile isSpace . snd) . reads