在我的Haskell程序中,我想读取用户使用getLine
函数给出的值。然后,我想使用read
函数将此值从字符串转换为适当的Haskell类型。如何捕获read
函数抛出的解析错误并要求用户重新输入值?
我是否正确地认为这不是“IO错误”,因为它不是由于IO系统无法正常运行而导致的错误?这是一个语义错误,所以我不能使用IO错误处理机制?
答案 0 :(得分:27)
你不想。您想要使用reads,可能就是这样:
maybeRead = fmap fst . listToMaybe . reads
(尽管你可能想要在元组的第二个元素不是""
时出错,也就是说,如果还有一个剩余的字符串)
原因为什么要使用读取而不是捕获error
异常,纯代码中的异常是邪恶的,因为非常很容易尝试抓住他们在错误的地方:注意他们只有在被迫时才会飞,而不是之前。找到那里可能是一个非平凡的练习。这是(原因之一)为什么Haskell程序员喜欢保持他们的代码总数,即终止和无异常。
答案 1 :(得分:8)
这是@ barsoap答案的补遗。
Haskell异常可能被抛出,包括纯代码,但它们只能从IO monad中捕获。为了捕获纯代码抛出的异常,您需要在IO语句中使用catch
或try
来强制对纯代码进行求值。
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,但parsec,polyparse和其他许多人也很好。不管怎样,你很可能不久就需要额外的电力。
答案 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