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

dav*_*gan 22 io haskell exception-handling

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

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

bar*_*oap 28

你不想.你想要使用read,可能是这样的:

maybeRead = fmap fst . listToMaybe . reads
Run Code Online (Sandbox Code Playgroud)

(虽然如果元组的第二个元素不是"",你可能想要错误,也就是说,如果剩下的字符串也是如此)

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

您可能想要查看正确的解析框架(例如parsec)和haskeline.

  • `maybeRead`在hackage(以及其他)中的`utility-ht`和`applicative-extras`包中,并且建议包含在base中.我不确定该提案的状态. (8认同)

Joh*_*n L 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
Run Code Online (Sandbox Code Playgroud)

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

现在,这就是纯代码中的异常是邪恶的原因.如果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
Run Code Online (Sandbox Code Playgroud)

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

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


ros*_*ree 7

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