тересующийся читатель может обратиться к книге
таких библиотек. Язык C является императивным, поэтому, применяя его функций в Haskell, мы неизбежно
сталкиваемся с типом IO
, поскольку большинство интересных функций в С изменяют состояние своих аргу-ментов. В С пишут и чистые функции, такие функции переносятся в Haskell без потери чистоты, но это не
всегда возможно.
В этой главе мы напишем небольшую 2D-игру, подключив две FFI-библиотеки, это графическая библио-
тека OpenGL
и физический движок Chipmunk.Описание игры
Игра происходит на бильярдной доске. Игрок управляет красным шаром, кликнув в любую точку экрана,
он может изменить направление вектора скорости красного шара. Шар покатится туда, куда кликнул пользо-
ватель в последний раз. Из луз будут вылетать шары трёх типов: синие, зелёные и оранжевые. Столкновение
красного шара с синим означает минус одну жизнь, с зелёным – плюс одну жизнь, оранжевый шар означает
бонус. Если шар игрока сталкивается с оранжевым шаром все шары в определённом радиусе от места столк-
новения исчезают и записываются в бонусные очки, за каждый шар по одному очку, при этом шар с которым
произошло столкновение не считается. Все столкновения – абсолютно упругие, поэтому при столкновении
энергия сохраняется и шары никогда не остановятся. Если шар попадает в лузу, то он исчезает. Если в лузу
попал шар игрока – это означает, что игра окончена. Игрок стартует с несколькими жизнями, когда их чис-
ло подходит к нулю игра останавливается. После столкновения с зелёным шаром, шар пропадает, а после
столкновения с синим – нет. В итоге все против игрока, кроме зелёных и оранжевых шаров.
20.1 Основные библиотеки
Контролировать физику игрового мира будет библиотека Chipmunk
, а библиотека OpenGL будет рисовать(конечно если мы её этому научим). Пришло время с ними познакомится.
288 | Глава 20: Императивное программирование
Изменяемые значения
Перед тем как мы перейдём к библиотекам нам нужно узнать ещё кое-что. В Haskell мы не можем изменять
значения. Но в С это делается постоянно, а соответственно и в библиотеках написанных на С тоже. Для того
чтобы имитировать в Haskell механизм обновления значений были придуманы специальные типы. Мы можем
объявить изменяемое значение и обновлять его, но только в пределах типа IO
.IORef
Тип IORef
из модуля Data.IORef описывает изменяемые значения:newIORef ::
a -> IO IORefreadIORef
:: IORef
a -> IO awriteIORef
:: IORef
a -> a -> IO ()modifyIORef :: IORef
a -> (a -> a) -> IO ()Функция newIORef создаёт изменяемое значение и инициализирует его некоторым значением, кото-
рые мы можем считать с помощью функции readIORef или обновить с помощью функций writeIORef или
modifyIORef. Посмотрим как это работает:
module Main where
import Data.IORef
main =
var >>= (\v ->readIORef v >>=
print>>
writeIORef v 4>>
readIORef v >>= print)where
var = newIORef 2Теперь посмотрим на ответ ghci:
*Main> :
l HelloIORef[1 of
1] Compiling Main( HelloIORef.
hs, interpreted )Ok
, modules loaded: Main.*Main>
main2
4
Самое время вернуться к главе 17 и вспомнить о do
-нотации. Такой императивный код гораздо нагляднееписать так:
main = do
var <-
newIORef 2x <-
readIORef varprint x
writeIORef var 4
x <-
readIORef varprint x
Эта запись выглядит как последовательность действий. Не правда ли очень похоже на обычный импера-
тивный язык. Такие переменные встречаются очень часто в библиотеках, заимствованных из~С.
StateVar
В модуле Data.StateVar
определены типы, которые накладывают ограничение на права по чтению изаписи. Мы можем определять переменные доступные только для чтения (GettableStateVar
a), только длязаписи (SettableStateVar
a) или обычные изменяемые переменные (SetVar a).Операции чтения и записи описываются с помощью классов:
class HasGetter
s whereget ::
s a -> IO aclass HasSetter
s where($=
) :: s a -> a -> IO ()Основные библиотеки | 289
Тип IORef
принадлежит и тому, и другому классу:main = do
var <-
newIORef 2x
<-
get varprint x
var $=
4x
<-
get varprint x
OpenGL
OpenGL
является ярким примером библиотеки построенной на изменяемых переменных. OpenGL можнопредставить как большой конечный автомат. Каждая строчка кода – это запрос на изменение состояния. При-
чём этот автомат является глобальной переменной. Его текущее состояние зависит от всей цепочки преды-
дущих команд. Параметры рисования задаются глобальными переменными (тип StateVar
).OpenGL
не зависит от конкретной оконной системы, она отвечает лишь за рисование. Для того чтобысоздать окно и перехватывать в нём действия пользователя нам понадобится отдельная библиотека. Для