которое указывает на сложность новой игры, также игрок может просто сделать ход Play Move.
А каков формат наших ответов? Все наши ответы на самом деле будут вызовами функции putStrLn мы
будем отвечать пользователю изменениями экрана. Поэтому у нас нет специального типа для ответов. Итак
у нас есть каркас, который можно начинать покрывать значениями. На этом этапе у нас есть два модуля. Это
модуль Loop:
module Loop where
import Game
data Query = Quit | NewGame Int | Play Move
202 | Глава 13: Поиграем
И модуль Game:
module Game where
import Data.Array
data Move = Up | Down | Left | Right
deriving (Enum)
type Label = Int
type Pos = (Int, Int)
type Board = Array Pos Label
data Game = Game {
emptyField
:: Pos,
gameBoard
:: Board }
Ленивое программирование
Мы уже знаем как происходят ленивые вычисления. Мы принимаем выражение и начинаем очищать его
от синонимов от корня к листьям или сверху вниз. Оказывается таким способом можно писать программы.
Более того в функциональном программировании это очень распространённый подход. Мы начинаем со
спецификации задачи (неформального описания) и потихоньку вытягиваем из него выражения языка Haskell.
Начинаем мы с корня, с самой верхней функции. Эта функция будет состоять из подвыражений. Когда мы
напишем верхнюю функцию, мы перейдём к подвыражениям. И так мы будем спускаться пока не напишем
всю программу.
Кажется, что такой подход очень не надёжен. Ведь мы сможем запустить программу только когда напи-
шем её целиком. На каждом промежуточном шаге у нас есть неопределённые подвыражения. Получается,
что очень долгое время мы будем писать программу, не зная работает она или нет.
Оказывается, что в Haskell есть решение этой проблемы. Нам поможет значение undefined. Мы будем
писать только тип функции (и мысленно будем говорить, пусть она делает то-то), а вместо определения
будем писать undefined. При этом конечно мы не сможем выполнять программу, вычислитель подорвётся
на первом же значении, но мы сможем узнать осмысленна ли наша программа с точки зрения компилятора,
проходит ли она проверку типов. В Haskell это большой плюс. Если программа прошла проверку типов, то
скорее всего она будет работать.
Такой подход написания программ называется написанием сверху вниз. Мы начинаем с самой верхней
функции и потихоньку вычищаем все undefined. Если вспомнить ленивые вычисления, то там роль undefined
выполняли отложенные вычисления.
В чём преимущества такого подхода? Посмотрим на дерево (рис. ?? ). Если мы идём сверху вниз, то в
самом начале у нас лишь одна задача, потом их становится всё больше и больше. Они дробятся, но источ-
ник у них один. Мы всегда знаем, что нам нужно чтобы закончить нашу задачу. Написать это, это и это
подвыражение. Беда только в том, что это подвыражение содержит ещё больше подвыражений. Но сложные
подвыражения мы можем оставить на потом и заняться другими. А потом, когда мы их доделаем может вдруг
оказаться, что это сложное выражение нам и не нужно.
Рис. 13.2: Дерево задач
Стратегия написания программ | 203
Если же мы начинаем идти из листьев, то у нас много отправных точек, которые должны сойтись в одной
цели. При этом они могут и не сойтись, мы можем застрять в одной точке и потратить слишком много
времени. И на остальные задачи у нас не хватит сил или мы можем потратить много времени на решение
задачи, которая совсем не нужна для итогового решения. Также как и в вычислениях по значению, мы можем
застрять на вычислении бесконечного значения, даже если в итоговом ответе нам понадобится лишь его
малая часть.
Ещё один плюс решения сверху вниз состоит в экономии усилий. Мы можем написать всю программу в
виде функций, которые состоят лишь из определений типов. И утрясти общую схему программы на типах.
Также при реализации отдельных частей программы, мы можем воспользоваться упрощёнными алгорит-
мами, достаточными для тестирования приложения, оставив отрисовку деталей на потом. Мы не тратим
время на реализацию, а смотрим как программа выглядит “вцелом”. Если общий набросок нас устраивает
мы можем начать заполнять дыры и детализировать отдельные выражения. Так мы будем детализировать-
детализировать пока не придём к первоначальному решению. Далее если у нас останется время мы можем
сменить реализацию некоторых частей. Но общая схема останется прежней, она уже устоялась на уровне ти-