私は簡単なゲーム - テトリスを書いています。私の人生で初めて、その目標のために関数型プログラミングを使用しています。言語として Haskell を選びました。しかし、私は OOP と命令型思考に染まっており、無意識のうちにこの考え方を Haskell プログラムに適用することを恐れています。
ゲームのどこかで、経過時間 (タイマー) と押し下げキー (キーボード) に関する情報が必要です。Haskell に変換された SDL レッスンで使用されるアプローチは次のようになります。
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
タイマー.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 はいくらか純粋になります (明示的にはモナドではなく、時間を測定するために必要なため、その関数は IO のみを返します)。ただし、Timer モジュールの外側のコードに getter と setter が散らばっています。
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 で OOP からオブジェクトを表現する私の方法であり、FP の要点全体を台無しにしてしまうのではないかと心配しています。だから私の質問は:
それを行う適切な方法は何ですか?または、そのような状況にアプローチする他の可能性は何ですか? また、他の欠陥 (デザインやスタイルの問題など) に気付いた場合は、遠慮なく指摘してください。