Как поймать исключение без разбора из функции чтения в Haskell? - PullRequest
21 голосов
/ 25 февраля 2011

В моей программе на Haskell я хочу прочитать значение, указанное пользователем с помощью функции getLine. Затем я хочу использовать функцию read для преобразования этого значения из строки в соответствующий тип языка Haskell. Как я могу отловить ошибки синтаксического анализа, выдаваемые функцией read, и попросить пользователя повторно ввести значение?

Правильно ли я считаю, что это не «ошибка ввода-вывода», поскольку это не ошибка, вызванная неправильной работой системы ввода-вывода? Это семантическая ошибка, поэтому я не могу использовать механизмы обработки ошибок ввода-вывода?

Ответы [ 4 ]

27 голосов
/ 25 февраля 2011

Ты не хочешь. Вместо этого вы хотите использовать читает , возможно, так:

maybeRead = fmap fst . listToMaybe . reads

(хотя может возникнуть ошибка, если вторым элементом кортежа не является "", то есть, если есть и оставшаяся строка)

Причина , почему вы хотите использовать чтение вместо перехвата error исключений, заключается в том, что исключения в чистом коде являются злом, потому что очень легко пытаться отловить их в неправильное место: обратите внимание, что они летают только тогда, когда их заставляют, а не раньше. Обнаружение, где это может быть нетривиальным упражнением. Вот почему (одна из причин), почему программисты на Haskell любят сохранять общий объем кода, то есть завершать его и не создавать исключений.

Возможно, вы захотите взглянуть на правильную структуру синтаксического анализа (например, parsec ) и haskeline .

8 голосов
/ 26 февраля 2011

Это дополнение к ответу @ barsoap больше, чем что-либо еще.

Исключения в Haskell могут создаваться где угодно, в том числе в чистом коде, но они могут быть перехвачены только из монады ввода-вывода.Чтобы перехватывать исключения, генерируемые чистым кодом, вам нужно использовать 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 не сможет проанализировать строку, но также вернет 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

Если вы запустите это, вы обнаружите, что вы всегда окажетесь в ветви Right оператора case,независимо от того, что пользователь вошел.Это связано с тем, что действие ввода-вывода, переданное try, никогда не пытается read введенной строки.Он печатает первое значение списка data, которое является постоянным, и никогда не касается конца списка.Таким образом, в первой ветви оператора case кодировщик думает, что данные оцениваются, но это не так, и read может по-прежнему выдавать исключение.

read предназначено для десериализации данных, а не для анализавведенный пользователем ввод.Используйте reads или переключитесь на настоящую библиотеку синтаксического анализатора.Мне нравится uu-parsinglib , но parsec , polyparse , и многие другие тоже хороши.В любом случае, очень скоро вам понадобится дополнительная мощность.

7 голосов
/ 26 октября 2014

Есть readMaybe и readEither, которые удовлетворяют вашим ожиданиям.Вы найдете эти функции в пакете Text.Read.

3 голосов
/ 12 апреля 2011

Вот улучшенный maybeRead, который допускает только конечные пробелы, но ничего больше:

import Data.Maybe
import Data.Char

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