Состояние с IO в Haskell для приложения CLI - PullRequest
2 голосов
/ 08 октября 2019

Я пытаюсь написать простую реализацию игры Hangman на Haskell, и у меня есть одна проблема - сохранить состояние игры.

Упрощенная / абстрактная версия моей проблемы выглядит так:

advanceState :: Char -> [Char] -> [Char]
advanceState = (:)

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

Обратите внимание, что я также пробовал версии с State [Char] a и StateT [Char] IO a безрезультатно.

Ответы [ 2 ]

2 голосов
/ 08 октября 2019

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

myProgram :: IO [Char]
myProgram = loop []
  where
    loop :: [Char] -> IO [Char]
    loop state = do
        input <- getLine
        case input of
            "A" -> do
                putStrLn "Advancing state with A"
                loop ('A' : state)
            "B" -> do
                putStrLn "State didn't change"
                loop state
            _ -> do
                putStrLn "Finishing game"
                pure state  -- recursion ends here

Таким образом, комбинация рекурсии и сопоставления с образцом позволяет вам изменять свое состояние в зависимости от вашего ввода. Поэтому, когда вы в следующий раз позвоните loop, ему дадут новый state.

1 голос
/ 08 октября 2019

При условии, что вы не знаете, как сохранить государство. Вот только простой пример, показывающий, как использовать StateT для чтения символов из стандартного ввода.

Во-первых, чтобы определить функцию advanceState как:

advanceState::StateT [Char] IO [Char]
advanceState = StateT readInput where
    readInput cs = do c <- getChar
                      let newState = (c:cs)
                      return (newState, newState)

Функция, встроенная в StateT, имеет значение readInput, она имеет тип:

[Char]->IO ([Char], [Char])

Первый элемент пары ([Char], [Char]) является вводом того, что мы хотим, а второй элемент этого состояния - «состояние» - будет передаваться в readInput всякий раз, когда advanceState вызывается снова.

Теперь нижеэто пример, показывающий, как использовать advanceState:

getChar10::StateT [Char] IO [Char]
getChar10 = do s <- advanceState
               if (length s) <= 10
               then getChar10
               else do put []
                       return (reverse s)

В этом примере показано, как получить 10 символов из стандартного ввода, когда пользователь нажимает клавишу ввода, обратите внимание, что put [] сбрасывает состояние так, чтобы следующий вызовadvanceState не будет иметь предыдущего ввода.

Наконец, мы можем поэкспериментировать с ними:

playState::IO ()
playState = print =<< evalStateT loop [] where
    loop  = do s1 <- getChar10
               s2 <- getChar10
               return (s1 ++ "::" ++ s2)  

Функция playState напечатает первые 20 символов (включая символ новой строки \n, \ r) сколько бы символов не было введено пользователем перед нажатием Enter. Если для ввода недостаточно 20 символов, playState попросит пользователя ввести больше символов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...