Стиль Haskell является функциональным, а не обязательным! Вместо того, чтобы «делать это потом», подумайте о комбинировании функций и описании того, что будет делать ваша программа, а не как.
В игре ваша программа запрашивает у пользователя предположение. Правильное предположение - победитель. В противном случае пользователь пытается снова. Игра продолжается, пока пользователь не угадает правильно, поэтому мы пишем, что:
main = untilM (isCorrect 42) (read `liftM` getLine)
При этом используется комбинатор, который многократно выполняет действие (getLine
извлекает строку ввода, а read
преобразует эту строку в целое число в этом случае) и проверяет ее результат:
untilM :: Monad m => (a -> m Bool) -> m a -> m ()
untilM p a = do
x <- a
done <- p x
if done
then return ()
else untilM p a
Предикат (частично примененный в main
) проверяет предположение относительно правильного значения и отвечает соответственно:
isCorrect :: Int -> Int -> IO Bool
isCorrect num guess =
case compare num guess of
EQ -> putStrLn "You Win!" >> return True
LT -> putStrLn "Too high!" >> return False
GT -> putStrLn "Too low!" >> return False
Действие до тех пор, пока игрок не угадает правильно:
read `liftM` getLine
Почему бы не сделать это простым и просто составить две функции?
*Main> :type read . getLine
<interactive>:1:7:
Couldn't match expected type `a -> String'
against inferred type `IO String'
In the second argument of `(.)', namely `getLine'
In the expression: read . getLine
Тип getLine
равен IO String
, но read
хочет чистый String
.
Функция liftM
из Control.Monad берет чистую функцию и «поднимает» ее в монаду. Тип выражения многое говорит нам о том, что он делает:
*Main> :type read `liftM` getLine
read `liftM` getLine :: (Read a) => IO a
Это действие ввода-вывода, которое при запуске возвращает нам значение, преобразованное с read
, * в нашем случае Int
. Напомним, что readLine
- это действие ввода-вывода, которое дает значения String
, поэтому вы можете думать о liftM
как о том, что он позволяет нам применять read
«внутри» монады IO
.
Пример игры:
1
Too low!
100
Too high!
42
You Win!