Читаем Учебник по Haskell полностью

data Move = Up | Down | Left | Right

Для того чтобы при каждом ходе не искать пустую клетку, давайте сохраним её текущее положение. Тип

Game будет содержать текущее положение пустой клетки и положение фишек:

data Game = Game {

emptyField

:: Pos,

gameBoard

:: Board }

Вот и все типы для описания игры. Сохраним их в модуле Game. Теперь подумаем о типах для диалога

с пользователем. В этом модуле наверняка будет много функций с типом IO, потому что в нём происходит

взаимодействие с игроком. Но, что является каркасом для диалога?

Если мы хотим с кем-нибудь общаться, необходимо чтобы у нас был с собеседником общий язык, он и

будет каркасом для диалога. Вспомним, что мы ожидаем от пользователя. Пользователь может:

• Сделать ход

• Начать новую игру

• Выйти из игры

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

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

гур. При перемешивании мы стартуем из победного положения и начинаем случайным образом делать хо-

ды. Чем больше ходов мы сделаем тем сложнее будет собрать игру. Поэтому пользователь будет указывать

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

попрощаемся и выйдем из игры.

На основе этих рассуждений вырисовывается следующий тип для сообщений:

data Query = Quit | NewGame Int | Play Move

Значение типа Query (запрос) может быть константа Quit (выход), запрос новой игры NewGame с числом,

которое указывает на сложность новой игры, также игрок может просто сделать ход 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

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

Перейти на страницу:

Похожие книги

C++: базовый курс
C++: базовый курс

В этой книге описаны все основные средства языка С++ - от элементарных понятий до супервозможностей. После рассмотрения основ программирования на C++ (переменных, операторов, инструкций управления, функций, классов и объектов) читатель освоит такие более сложные средства языка, как механизм обработки исключительных ситуаций (исключений), шаблоны, пространства имен, динамическая идентификация типов, стандартная библиотека шаблонов (STL), а также познакомится с расширенным набором ключевых слов, используемым в .NET-программировании. Автор справочника - общепризнанный авторитет в области программирования на языках C и C++, Java и C# - включил в текст своей книги и советы программистам, которые позволят повысить эффективность их работы. Книга рассчитана на широкий круг читателей, желающих изучить язык программирования С++.

Герберт Шилдт

Программирование, программы, базы данных