сталкиваемся с типом IO, поскольку большинство интересных функций в С изменяют состояние своих аргу-
ментов. В С пишут и чистые функции, такие функции переносятся в Haskell без потери чистоты, но это не
всегда возможно.
В этой главе мы напишем небольшую 2D-игру, подключив две FFI-библиотеки, это графическая библио-
тека OpenGLи физический движок Chipmunk.
Описание игры
Игра происходит на бильярдной доске. Игрок управляет красным шаром, кликнув в любую точку экрана,
он может изменить направление вектора скорости красного шара. Шар покатится туда, куда кликнул пользо-
ватель в последний раз. Из луз будут вылетать шары трёх типов: синие, зелёные и оранжевые. Столкновение
красного шара с синим означает минус одну жизнь, с зелёным – плюс одну жизнь, оранжевый шар означает
бонус. Если шар игрока сталкивается с оранжевым шаром все шары в определённом радиусе от места столк-
новения исчезают и записываются в бонусные очки, за каждый шар по одному очку, при этом шар с которым
произошло столкновение не считается. Все столкновения – абсолютно упругие, поэтому при столкновении
энергия сохраняется и шары никогда не остановятся. Если шар попадает в лузу, то он исчезает. Если в лузу
попал шар игрока – это означает, что игра окончена. Игрок стартует с несколькими жизнями, когда их чис-
ло подходит к нулю игра останавливается. После столкновения с зелёным шаром, шар пропадает, а после
столкновения с синим – нет. В итоге все против игрока, кроме зелёных и оранжевых шаров.
20.1 Основные библиотеки
Контролировать физику игрового мира будет библиотека Chipmunk, а библиотека OpenGLбудет рисовать
(конечно если мы её этому научим). Пришло время с ними познакомится.
288 | Глава 20: Императивное программирование
Изменяемые значения
Перед тем как мы перейдём к библиотекам нам нужно узнать ещё кое-что. В Haskell мы не можем изменять
значения. Но в С это делается постоянно, а соответственно и в библиотеках написанных на С тоже. Для того
чтобы имитировать в Haskell механизм обновления значений были придуманы специальные типы. Мы можем
объявить изменяемое значение и обновлять его, но только в пределах типа IO.
IORef
Тип IORefиз модуля Data.IORefописывает изменяемые значения:
newIORef ::a -> IO IORef
readIORef
:: IORefa -> IOa
writeIORef
:: IORefa ->a -> IO()
modifyIORef :: IORefa ->(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)
wherevar =newIORef 2
Теперь посмотрим на ответ ghci:
*Main> :l HelloIORef
[1 of1] Compiling Main
( HelloIORef.hs, interpreted )
Ok, modules loaded : Main.
*Main>main
2
4
Самое время вернуться к главе 17 и вспомнить о do-нотации. Такой императивный код гораздо нагляднее
писать так:
main = do
var <-newIORef 2
x <-readIORef var
print x
writeIORef var 4
x <-readIORef var
print x
Эта запись выглядит как последовательность действий. Не правда ли очень похоже на обычный импера-
тивный язык. Такие переменные встречаются очень часто в библиотеках, заимствованных из~С.
StateVar
В модуле Data.StateVarопределены типы, которые накладывают ограничение на права по чтению и
записи. Мы можем определять переменные доступные только для чтения ( GettableStateVara), только для
записи ( SettableStateVara) или обычные изменяемые переменные ( SetVara).
Операции чтения и записи описываются с помощью классов:
class HasGetters where
get ::s a -> IOa
class HasSetters where
( $=) ::s a ->a -> IO()
Основные библиотеки | 289
Тип IORefпринадлежит и тому, и другому классу:
main = do
var <-newIORef 2
x
<-get var
print x
var $=4
x
<-get var
print x
OpenGL
OpenGLявляется ярким примером библиотеки построенной на изменяемых переменных. OpenGLможно
Читать дальше