Пишу простую игру - Тетрис. Впервые в жизни я использую функциональное программирование для этой цели, в качестве языка я выбрал Haskell. Однако я заражен ООП и императивным мышлением и боюсь неосознанно применять этот образ мышления в моей программе на Haskell.
Где-то в моей игре мне нужна информация о прошедшем времени (таймер) и нажатых / нажатых клавишах (клавиатура). Подход, используемый в уроках SDL, переведенных на Haskell, выглядит так:
Main.hs
data AppData = AppData {
fps :: Timer
--some other fields
}
getFPS :: MonadState AppData m => m Timer
getFPS = liftM fps get
putFPS :: MonadState AppData m => Timer -> m ()
putFPS t = modify $ \s -> s { fps = t }
modifyFPSM :: MonadState AppData m => (Timer -> m Timer) -> m ()
modifyFPSM act = getFPS >>= act >>= putFPS
Timer.hs
data Timer = Timer {
startTicks :: Word32,
pausedTicks :: Word32,
paused :: Bool,
started :: Bool
}
start :: Timer -> IO Timer
start timer = SdlTime.getTicks >>= \ticks -> return $ timer { startTicks=ticks, started=True,paused=False }
isStarted :: Timer -> Bool
isStarted Timer { started=s } = s
А потом использовал вот так: modifyFPSM $ liftIO . start
. Это делает Timer в некоторой степени чистым (это не явно монада, и его функции возвращают ввод-вывод только потому, что он требуется для измерения времени). Однако это засоряет код вне модуля Timer геттерами и сеттерами.
Мой подход, используемый в Keyboard.hs:
data KeyboardState = KeyboardState {
keysDown :: Set SDLKey, -- keys currently down
keysPressed :: Set SDLKey -- keys pressed since last reset
};
reset :: MonadState KeyboardState m => m ()
reset = get >>= \ks -> put ks{keysPressed = Data.Set.empty}
keyPressed :: MonadState KeyboardState m => SDLKey -> m ()
keyPressed key = do
ks <- get
let newKeysPressed = Data.Set.insert key $ keysPressed ks
let newKeysDown = Data.Set.insert key $ keysDown ks
put ks{keysPressed = newKeysPressed, keysDown = newKeysDown}
keyReleased :: MonadState KeyboardState m => SDLKey -> m ()
keyReleased key = do
ks <- get
let newKeysDown = Data.Set.delete key $ keysDown ks
put ks{keysDown = newKeysDown}
Это делает модуль самодостаточным, но я боюсь, что это мой способ выразить объект из ООП в Haskell и разрушить всю суть FP. Итак, мой вопрос:
Как правильно это сделать? Или как еще можно подойти к такой ситуации? И если вы заметите какие-либо другие недостатки (будь то проблемы с дизайном или стилем), не стесняйтесь указать на это.
IO
объясняет, как построить нечистую программу, но самиIO
значения чистые. 2. В большинстве таймеров нет смысла говорить, что он приостановлен, если он не был запущен. Чтобы исключить это как возможное состояние, в котором может находиться значение, вы можете заменить эти два поля одним полем, содержащим что-то вродеdata TimerStatus = Stopped | Running | Paused
. - person Carl   schedule 27.12.2013