oop - Haskell - 状态单子(monad)是命令式思维的标志吗?

标签 oop haskell game-engine monads state-monad

我正在写一个简单的游戏——俄罗斯方 block 。这是我有生以来第一次使用函数式编程来实现这个目标,我选择了 Haskell 作为一门语言。然而,我被 OOP 和命令式思维所污染,并且害怕无意识地将这种思维方式应用到我的 Haskell 程序中。

在我的游戏中,我需要了解耗时(计时器)和按下/按下键(键盘)的信息。翻译成 Haskell 的 SDL 类(class)中使用的方法如下所示:

主文件

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 有点纯粹(它不是明确的 monad,它的函数返回 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 的全部意义。所以我的问题是:

这样做的正确方法是什么?或者还有什么其他可能性来处理这种情况?如果您发现任何其他缺陷(无论是设计还是风格问题),请随时指出。

最佳答案

大多数程序都有一些状态概念。让您不用担心每次使用State monad 以某种方式形成或形成。它仍然是纯粹的功能,因为你本质上是在写

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

但是不要编写状态单子(monad)的组合器,而是考虑将它们编写为简单的纯函数,然后使用 modify将它们注入(inject)状态单子(monad)。
reset :: KeyBoard -> KeyBoard
keyPressed :: Key -> KeyBoard -> KeyBoard
...

然后当你真正想要状态时,这些很容易使用
 do
   nextKey <- liftIO $ magic
   modify $ keyPressed nextKey

如果你想在纯函数中使用它们,你不再需要用它们来拖拽整个 state monad,这使得构建组合器变得更简单一些。

TLDR:一点点状态也不错,甚至可以让代码更容易理解,但是把它拖到代码的每一部分都是不好的。

关于oop - Haskell - 状态单子(monad)是命令式思维的标志吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20803322/

相关文章:

Haskell:使用镜头修改状态时 TChan 包装重复

haskell - 为什么我不能将其从 Monad 推广到 Applicative?

haskell - HXT 获取第一个元素 : refactor weird arrow

javascript - 我怎样才能一次捕捉到 2+ 个按键?

php - 在转向框架之前,您需要具备良好的 OOP 背景吗?

java - 通过设计模式重构交换机机壳的旧实例

c++ - Visual Studio 2019 找不到很多头文件

C++:外部对象循环引用

oop - 公司层次结构类图

java - Java 中范围和包的问题