цели. При этом они могут и не сойтись, мы можем застрять в одной точке и потратить слишком много
времени. И на остальные задачи у нас не хватит сил или мы можем потратить много времени на решение
задачи, которая совсем не нужна для итогового решения. Также как и в вычислениях по значению, мы можем
застрять на вычислении бесконечного значения, даже если в итоговом ответе нам понадобится лишь его
малая часть.
Ещё один плюс решения сверху вниз состоит в экономии усилий. Мы можем написать всю программу в
виде функций, которые состоят лишь из определений типов. И утрясти общую схему программы на типах.
Также при реализации отдельных частей программы, мы можем воспользоваться упрощёнными алгорит-
мами, достаточными для тестирования приложения, оставив отрисовку деталей на потом. Мы не тратим
время на реализацию, а смотрим как программа выглядит “вцелом”. Если общий набросок нас устраивает
мы можем начать заполнять дыры и детализировать отдельные выражения. Так мы будем детализировать-
детализировать пока не придём к первоначальному решению. Далее если у нас останется время мы можем
сменить реализацию некоторых частей. Но общая схема останется прежней, она уже устоялась на уровне ти-
пов. Часто такую стратегию разработки называют разработкой через прототипы (developing by prototyping).
При этом процесс написания приложения можно представить как процесс сходимости, приближения к преде-
лу. У нас есть серия промежуточных решений или прототипов, которые с каждым шагом всё точнее и точнее
описывают итоговую программу. Также если мы работаем в команде, то дробление задачи на подзадачи про-
исходит естественно, в ходе детализации, мы можем распределить нагрузку, распределив разные undefined
между участниками проекта.
Слово undefined будет встречаться очень часто, буквально в каждом значении. Оно очень длинное, и
часто писать его будет слишком утомительно. Определим удобный синоним. Я обычно использую un или
lol (что-нибудь краткое и удобное для автоматического поиска):
un ::
aun =
undefinedНо давайте приступим к реализации нашей игры. Самая верхняя функция, будет запускать программу.
Назовём её play. Это функция взаимодействия с пользователем она ведёт диалог, поэтому её тип будет IO
():
play :: IO
()play =
unИтак у нас появилась корневая функция. Что мы будем в ней делать? Для начала мы поприветствуем игро-
ка (функция greetings). Затем предложим ему начать игру (функция setup), после чего запустим цикл игры
(функция gameLoop). Приветствие это просто надпись на экране, поэтому тип у него будет IO
(). Предложе-ние игры вернёт стартовую позицию для игры, поэтому тип будет IO Game
. Цикл игры принимает состояниеи продолжает диалог. В типах это выражается так:
play :: IO
()play =
greetings >> setup >>= gameLoopgreetings :: IO
()greetings =
unsetup :: IO Game
setup =
ungameLoop :: Game -> IO
()gameLoop =
unСохраним эти определения в модуле Loop
и загрузим модуль с программой в интерпретатор:Prelude> :
l Loop[1 of
2] Compiling Game( Game.
hs, interpreted )[2 of
2] Compiling Loop( Loop.
hs, interpreted )Ok
, modules loaded: Game, Loop.*Loop>
Модуль загрузился. Он потянул за собой модуль Game
, потому что мы воспользовались типом Move изэтого модуля. Программа прошла проверку типов, значит она осмысленна и мы можем двигаться дальше.
У нас три варианта дальнейшей детализации это функции greetings, setup и gameLoop. Мы пока пропу-
стим greetings там мы напишем какое-нибудь приветствие и сообщим игроку куда он попал и как ходить.
204 | Глава 13: Поиграем
В функции setup нам нужно начать первую игру. Для начала игры нам нужно узнать её сложность, на
сколько ходов перемешивать позицию. Это значит, что нам нужно спросить у игрока целое число. Мы спро-
сим число функцией getLine, а затем попробуем его распознать. Если пользователь ввёл не число, то мы
попросим его повторить ввод. Функция readInt :: String -> Maybe Int
распознаёт число. Она возвращаетцелое число завёрнутое в Maybe
, потому что строка может оказаться не числом. Затем это число мы исполь-зуем в функции shuffle (перемешать), которая будет возвращать позицию, которая перемешана с заданной
глубиной.
-- в модуль Loop
setup :: IO Game
setup =
putStrLn ”Начнём новую игру?” >>putStrLn ”Укажите сложность (положительное целое число): ” >>
getLine >>=
maybe setup shuffle . readIntreadInt :: String -> Maybe Int
readInt =
un-- в модуль Game:
shuffle :: Int -> IO Game
shuffle =
unФункция shuffle возвращает состояние игры Game
, которое завёрнуто в IO. Оно завёрнуто в IO, потомучто перемешивать позицию мы будем случайным образом, это значит, что мы воспользуемся функциями из
модуля Random
. Мы хотим чтобы каждая новая игра начиналась с новой позиции, поэтому скорее всего где-тов недрах функции shuffle мы воспользуемся newStdGen, которая и потянет за собой тип IO
.