Haskell - Попытка получить выходной тип с использованием Prelude.read, чтобы также работать для String - PullRequest
2 голосов
/ 01 мая 2020

Я делал небольшую программу, которую никогда не ставил на github и не распространял, и заметил, что в командной строке просят что-то задавать, получать ответ и использовать его. Поэтому я хотел превратить этот шаблон в функцию.

askQuestion :: (Read a) => String -> IO a
askQuestion q = do
     putStrLn q
     read <$> getLine

Конечно, функция такого типа не особенно безопасна, поскольку выдает ошибку, когда ответ пользователя не может быть проанализирован. , Именно поэтому readMaybe или readEither рекомендуется. Но так как я единственный пользователь кода, я не против, потому что я знаю, что нужно для моей программы.

После этого я подумал о другом шаблоне, который мой код использовал много, который просит пользователя выбрать между двумя вариантами, после чего моя программа разветвляется в зависимости от ответа. Я также сделал для этого функцию.

askOption :: String -> String -> String -> IO a -> IO a -> IO a
askOption q a1 a2 ac1 ac2 = do
   putStrLn q
   ans <- getLine
   if a1 == ans
    then ac1
    else if a2 == ans
          then ac2
          else error $ "Not one of the options for question " ++ q

Этот код отлично работал для того, что я хотел, и мои основные функции выглядели довольно чисто. Но когда я заметил

putStrLn q
ans <- getLine

в своей функции askOption, я захотел заменить этот блок функцией askQuestion.

В этом и заключается моя проблема, очевидно, чтение не может проанализировать строку для строка. Теперь, я думаю, было совершенно очевидно, что вы не можете этого ожидать, но я надеялся, что read просто переместит строку, ничего не делая с ней.

Что я хотел бы знать, так это то, возможно ли иметь систему, в которой если read замечает, что это строка, она просто пропускает ее. Я хочу сохранить чистый тип ввода-вывода a и не выполнять рефакторинг своего кода для использования IO Maybe a или IO Either String a. Я знаю, что могу обработать ошибку, а затем в дескрипторе использовать getLine, чтобы получить ответ. Но это потребовало бы, чтобы я набирал ответ дважды каждый раз. Один раз потерпеть неудачу и один раз, когда это удастся.

ans <- handle (\(SomeException e) -> putStrLn "Answer again" >> getLine) $ askQuestion "Give Input"  

Это сохранит нужный мне тип, но будет проблемой для самой программы.

Другой вариант, о котором я подумал, это проверка, является ли ans строкой в ​​функции askQuestion, и если это было возвращение строки и выполнение чтения. Но это нарушило бы тип ввода-вывода, поскольку строка ввода-вывода не равна значению ввода-вывода.

Резюме: есть ли способ сделать так, чтобы сигнатура типа askQuestion оставалась ввода-вывода во время передачи ответов, которые не могут читать как строки. Я знаю, что использование readEither / readMaybe предпочтительнее в реальной практике, но я просто хотел сделать это для развлечения.

1 Ответ

2 голосов
/ 01 мая 2020

Вы не можете сделать это с вашей существующей функцией askQuestion, потому что в ней встроено read, и вы не можете sh использовать read в этом случае. Чтобы сделать эту функцию более гибкой, извлеките из нее параметр:

askQuestion' :: (String -> a) -> String -> IO a
askQuestion' parse prompt = do
  putStrLn prompt 
  parse <$> getLine

Тогда ваш исходный askQuestion может быть определен как:

askQuestion :: (Read a) => String -> IO a
askQuestion = askQuestion' read

и ваш новый askOption может использовать более гибкая функция:

askOption :: String -> String -> String -> IO a -> IO a -> IO a
askOption q a1 a2 ac1 ac2 = do
   ans <- askQuestion' id q
   if a1 == ans
    then ac1
    else if a2 == ans
          then ac2
          else error $ "Not one of the options for question " ++ q
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...