简体   繁体   中英

How is state persisted between function invocations in a functional programming language?

I have just started my foray into the functional paradigm from an OO background. I'm confused how state is persisted between function invocations in a functional programming language.

Consider an example of a game that you are creating that you intend to release as a library for others to use. Others will use your game code as the domain of their game, and hook it up to their own UI. Using an OO approach, one might expect the client to use your game code like so:

main() {
    Game game = new Game();

    while(userHasNotQuit()) {
        // respond to user input
        // call some method that mutates the internal data of the game object
            // ex: game.increaseScore();
        // get the data out of the game object
        // display it on screen
    }
}

Here, the internal data structure of the Game type is hidden from the client and the Game type can expose a public api that defines exactly how the client may interact with the game. It can achieve data and function hiding because of OO access modifiers.

I can't seem to figure out how a functional style would work in this situation. Would the client code need to hold a reference to a data structure and pass that data structure to free functions? Something like:

main() {
    GameData gameData = createGame(); // structure of data with no methods.

    while(userHasNotQuit()) {
        // respond to user input
        // call some function that returns a transformed gameData
            // ex: gameData = increaseScore(gameData);
        // get the data out of the game object
        // display it on screen
    }
}

How would you achieve only exposing certain functions that define your public api, or only exposing certain data from your game data structure?

FP does not do away with state, that would make it rather hard to do anything useful with it. What it does eschew is non-local mutable state, because it breaks referential transparency.

This is not hard to do. You just take all that state you would access and mutate in the imperative version, and put it in a data structure that you thread through all the iterations of your game loop. Which is what I think you allude to. Here's an example of a straightforward F# translation of how such a game loop could be structured.

 let rec loop 
         (exitCondition: UserInputs -> GameState -> bool) 
         (update: UserInputs -> GameState -> GameState) 
         (draw: GameState -> unit) 
         (state: GameState) = 
     let inputs = getUserInputs()
     if exitCondition inputs state
         then ()
         else
             let updated = update inputs state
             draw updated 
             loop exitCondition update draw updated

It's a higher order function that you give an initial state together with a function to update the state on each step, a draw function that has a side effect of drawing a frame of the game on the screen and one more function to check the exit condition.

This gives you a well-defined interface for updating the game state - all the updates happen as part of update function and you can be sure that nothing you do in exitCondition or draw interferes with that.

As for data hiding, this is typically less of a concern in FP - as the state is not mutable and all the changes a function makes are explicit in the return value, there's less of a fear around giving access to data to the users of your API. It's not like they can break something inside by mutating it at will. You could however split that state into two separate parts and only pass in the "public" one to the update function (by making it an update: UserInputs -> PublicGameState -> PublicGameState instead).

While the above example is rather simplistic, it shows that as far as expressive power goes, you can write a game in an FP language. Here's a nice read about applying a similar functional approach to games.

A separate topic is functional reactive programming, which has a bit of a different flavour to it. Here's Yan Cui's talk about writing a simple game in ELM, which you might also find interesting.

As a really simple example, you can do something like

// pseudocode
function main() {

  function loop(gameState) {
   // whatever in the loop
   // ...
   // loop with new state
   loop({a: (gameState.a+1) }); // {a: 2}, {a: 3}, {a: 4}, ...
  }

  // initialize with some state
  loop({a: 1});
}

Also see State Monad

Here's a Haskell example from their docs

Simple example that demonstrates the use of the standard Control.Monad.State monad . It's a simple string parsing algorithm.

module StateGame where

import Control.Monad.State

-- Example use of State monad
-- Passes a string of dictionary {a,b,c}
-- Game is to produce a number from the string.
-- By default the game is off, a C toggles the
-- game on and off. A 'a' gives +1 and a b gives -1.
-- E.g 
-- 'ab'    = 0
-- 'ca'    = 1
-- 'cabca' = 0
-- State = game is on or off & current score
--       = (Bool, Int)

type GameValue = Int
type GameState = (Bool, Int)

playGame :: String -> State GameState GameValue
playGame []     = do
    (_, score) <- get
    return score

playGame (x:xs) = do
    (on, score) <- get
    case x of
         'a' | on -> put (on, score + 1)
         'b' | on -> put (on, score - 1)
         'c'      -> put (not on, score)
         _        -> put (on, score)
    playGame xs

startState = (False, 0)

main = print $ evalState (playGame "abcaaacbbcabbab") startState

Your example presupposes that there is some sort of global game data outside of the game object. This is quite antithetical to functional programming. "Fixing" it makes your example quite uninteresting and uninformative; but also, in many ways, better:

main() {
    GameState gameState = createGame();

    while(gamestate.userHasNotQuit()) {
        // respond to user input
        // call some function that transforms gameData
            // ex: gameData.increaseScore();
        // make the gameData object
        // display the game on screen
    }
}

As a matter of fact, this is probably a better way to do it in the OOP world, too. The game state is what the program manipulates, and so the last inch of this change would be to simply call gameData.main() instead of having the external program know anything about its internals or state changes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM