Подтвердить что ты не робот

Как поймать исключение без синтаксиса из функции чтения в Haskell?

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

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

4b9b3361

Ответ 1

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

maybeRead = fmap fst . listToMaybe . reads

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

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

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

Ответ 2

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

Исключения Haskell могут быть выброшены где угодно, в том числе в чистом коде, но их можно поймать только из монады 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 не может проанализировать строку, но также вернет 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, вне зависимости от того, что пользователь вводил. Это связано с тем, что действие IO, переданное в try, никогда не пытается ввести read введенную строку. Он печатает первое значение списка data, которое является постоянным и никогда не касается хвоста списка. Таким образом, в первой ветки оператора case кодер считает, что данные оцениваются, но это не так, и read может все же исключить исключение.

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

Ответ 3

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

Ответ 4

Здесь улучшен maybeRead, который позволяет использовать только пробелы в пробелах, но ничего больше:

import Data.Maybe
import Data.Char

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