Pregunta Haskell: ¿es la mónada estatal un signo de pensamiento imperativo?


Estoy escribiendo un juego simple - Tetris. Por primera vez en mi vida estoy usando programación funcional para ese objetivo, como lenguaje elegí a Haskell. Sin embargo, estoy contaminado con OOP y pensamiento imperativo y miedo de aplicar inconscientemente esta forma de pensar a mi programa Haskell.

En algún lugar de mi juego, necesito tener información sobre el tiempo transcurrido (temporizador) y las teclas presionadas / abajo (Teclado). El enfoque utilizado en las lecciones de SDL traducidas a Haskell se ve así:

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

Y luego usado así: modifyFPSM $ liftIO . start. Eso hace que el temporizador sea algo puro (no es explícitamente una mónada, y sus funciones devuelven IO solo porque es necesario para medir el tiempo). Sin embargo, eso ensucia el código fuera del módulo del temporizador con captadores y configuradores.

Mi enfoque utilizado en Keyboard.hs es:

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}

Eso hace que el módulo sea autónomo, pero me temo que esta es mi forma de expresar el objeto desde OOP en Haskell y arruina todo el punto de FP. Entonces mi pregunta es:

¿Cuál es la forma correcta de hacer eso? ¿O cuáles son las otras posibilidades para abordar esa situación? Y si nota cualquier otro defecto (ya sea de diseño o de estilo), siéntase libre de señalarlo.


5
2017-12-27 15:35


origen


Respuestas:


La mayoría de los programas tienen alguna noción de estado. Así que no tiene que preocuparse cada vez que use el State mónada de alguna forma o forma. Todavía es pura función ya que esencialmente estás escribiendo

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

Pero en lugar de escribir los combinadores de la mónada de estado, en su lugar, considera escribirlos como funciones puras simples y luego usar modify para inyectarlos en la mónada del estado.

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

Y luego, cuando realmente quieres un estado, estos son fáciles de usar

 do
   nextKey <- liftIO $ magic
   modify $ keyPressed nextKey

Y si desea utilizarlos en funciones puras, ya no arrastra la mónada de estado completo con ellos, lo que facilita un poco la creación de combinadores.

TLDR: un pequeño estado no es malo, e incluso puede hacer que el código sea más fácil de entender, pero arrastrarlo a cada parte de tu código es malo.


7
2017-12-27 16:06



Contrariamente a la creencia popular, la filosofía Haskell no se trata de eliminar el estado, sino de hacer explícito el estado, encapsularlo y controlarlo. Siéntase libre de usar la mónada estatal al contenido de su corazón, si hace que su código sea más claro.

Haskell es muy bueno en la abstracción y te permitirá expresar los conceptos que deseas en tu juego a un nivel más alto que el que tienes aquí. Probablemente te gustaría mirar en "Programación reactiva funcional"


2
2017-12-27 17:26