haskell-notes

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

времени. И на остальные задачи у нас не хватит сил или мы можем потратить много времени на решение

задачи, которая совсем не нужна для итогового решения. Также как и в вычислениях по значению, мы можем

застрять на вычислении бесконечного значения, даже если в итоговом ответе нам понадобится лишь его

малая часть.

Ещё один плюс решения сверху вниз состоит в экономии усилий. Мы можем написать всю программу в

виде функций, которые состоят лишь из определений типов. И утрясти общую схему программы на типах.

Также при реализации отдельных частей программы, мы можем воспользоваться упрощёнными алгорит-

мами, достаточными для тестирования приложения, оставив отрисовку деталей на потом. Мы не тратим

время на реализацию, а смотрим как программа выглядит “вцелом”. Если общий набросок нас устраивает

мы можем начать заполнять дыры и детализировать отдельные выражения. Так мы будем детализировать-

детализировать пока не придём к первоначальному решению. Далее если у нас останется время мы можем

сменить реализацию некоторых частей. Но общая схема останется прежней, она уже устоялась на уровне ти-

пов. Часто такую стратегию разработки называют разработкой через прототипы (developing by prototyping).

При этом процесс написания приложения можно представить как процесс сходимости, приближения к преде-

лу. У нас есть серия промежуточных решений или прототипов, которые с каждым шагом всё точнее и точнее

описывают итоговую программу. Также если мы работаем в команде, то дробление задачи на подзадачи про-

исходит естественно, в ходе детализации, мы можем распределить нагрузку, распределив разные undefined

между участниками проекта.

Слово undefined будет встречаться очень часто, буквально в каждом значении. Оно очень длинное, и

часто писать его будет слишком утомительно. Определим удобный синоним. Я обычно использую un или

lol (что-нибудь краткое и удобное для автоматического поиска):

un :: a

un = undefined

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

Назовём её play. Это функция взаимодействия с пользователем она ведёт диалог, поэтому её тип будет IO

():

play :: IO ()

play = un

Итак у нас появилась корневая функция. Что мы будем в ней делать? Для начала мы поприветствуем игро-

ка (функция greetings). Затем предложим ему начать игру (функция setup), после чего запустим цикл игры

(функция gameLoop). Приветствие это просто надпись на экране, поэтому тип у него будет IO (). Предложе-

ние игры вернёт стартовую позицию для игры, поэтому тип будет IO Game. Цикл игры принимает состояние

и продолжает диалог. В типах это выражается так:

play :: IO ()

play = greetings >> setup >>= gameLoop

greetings :: IO ()

greetings = un

setup :: IO Game

setup = un

gameLoop :: 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 . readInt

readInt :: String -> Maybe Int

readInt = un

— в модуль Game:

shuffle :: Int -> IO Game

shuffle = un

Функция shuffle возвращает состояние игры Game, которое завёрнуто в IO. Оно завёрнуто в IO, потому

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

модуля Random. Мы хотим чтобы каждая новая игра начиналась с новой позиции, поэтому скорее всего где-то

в недрах функции shuffle мы воспользуемся newStdGen, которая и потянет за собой тип IO.

Игра перемешивается согласно правилам, поэтому функцию shuffle мы поселим в модуле Game. А функ-

ция readInt это скорее элемент взаимодействия с пользователем, ведь в ней мы распознаём число в строчном

ответе, она останется в модуле Loop.

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162