5

私は簡単なゲーム - テトリスを書いています。私の人生で初めて、その目標のために関数型プログラミングを使用しています。言語として 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 の要点全体を台無しにしてしまうのではないかと心配しています。だから私の質問は:

それを行う適切な方法は何ですか?または、そのような状況にアプローチする他の可能性は何ですか? また、他の欠陥 (デザインやスタイルの問題など) に気付いた場合は、遠慮なく指摘してください。

4

2 に答える 2

7

ほとんどのプログラムには状態の概念があります。Stateしたがって、何らかの形でモナドを使用するたびに心配する必要はありません。あなたは本質的に書いているので、それはまだ純粋に関数です

Arg1 -> Arg2 -> State -> (State, Result)

しかし、状態モナドのコンビネータを書く代わりに、それらを単純な純粋な関数として書き、それを使用modifyして状態モナドに注入することを検討してください。

reset :: KeyBoard -> KeyBoard
keyPressed :: Key -> KeyBoard -> KeyBoard
...

そして、実際に状態が必要な場合、これらは簡単に使用できます

 do
   nextKey <- liftIO $ magic
   modify $ keyPressed nextKey

また、それらを純粋な関数で使用したい場合は、状態モナド全体を一緒にドラッグする必要がなくなり、コンビネーターの構築が少し簡単になります。

TLDR: 少しの状態は悪くなく、コードを理解しやすくすることさえできますが、それをコードのあらゆる部分にドラッグするのは良くありません。

于 2013-12-27T16:06:55.417 に答える
2

一般に信じられていることとは反対に、Haskell の哲学は状態を排除することではなく、状態を明示し、カプセル化し、制御することです。コードがより明確になるのであれば、自由に状態モナドを使用してください。

Haskell は抽象化に非常に優れており、ゲームに必要な概念をより高いレベルで表現することができます。あなたはおそらく「Functional Reactive Programming」を調べたいと思うでしょう。

于 2013-12-27T17:26:48.340 に答える