[英]How is state persisted between function invocations in a functional programming language?
我刚刚从面向对象的背景开始涉足功能范式。 我很困惑在函数式编程语言中如何在函数调用之间保持状态。
考虑一个您正在创建的游戏示例,您打算将其发布为库供其他人使用。 其他人会将您的游戏代码用作他们的游戏域,并将其连接到自己的UI。 使用OO方法,可能希望客户端使用您的游戏代码,如下所示:
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
}
}
在这里,游戏类型的内部数据结构对客户端是隐藏的,游戏类型可以公开一个公共api,该API准确定义了客户端如何与游戏进行交互。 由于OO访问修饰符,它可以实现数据和功能隐藏。
我似乎无法弄清楚在这种情况下功能样式将如何工作。 客户端代码是否需要保留对数据结构的引用并将该数据结构传递给自由函数? 就像是:
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
}
}
您如何才能仅公开定义公共api的某些功能,或仅公开游戏数据结构中的某些数据?
FP并不会消除状态,这会使它很难做任何有用的事情。 它回避的操作是非局部可变状态,因为它破坏了引用透明性。
这并不难。 您只需要在命令式版本中获取所有要访问和更改的状态,并将其置于一个数据结构中,即可在游戏循环的所有迭代中进行线程化。 我想这就是你所暗示的。 这是一个简单的F#转换示例,说明如何构建这样的游戏循环。
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
这是一个高阶函数,您可以赋予初始状态以及在每个步骤上更新状态的函数,还有一个具有在屏幕上绘制游戏框架的副作用的绘制函数,以及一个用于检查退出条件的函数。
这为您提供了一个定义良好的界面来更新游戏状态-所有更新都作为update
功能的一部分进行,并且您可以确保在exitCondition
中进行的任何操作或draw
不会exitCondition
干扰。
至于数据隐藏,通常在FP中较少关注-由于状态是不可变的,并且函数所做的所有更改在返回值中都是显式的,因此不必担心将数据访问权限授予您的用户API。 并不是说他们可以通过随意改变某些东西来破坏它们。 但是,您可以将该状态分为两个单独的部分,而仅将“公共”部分传递给更新函数(通过对其进行update: UserInputs -> PublicGameState -> PublicGameState
改为使用update: UserInputs -> PublicGameState -> PublicGameState
)。
尽管上面的示例非常简单,但是它表明,只要表现力强,就可以使用FP语言编写游戏。 这是有关将类似功能方法应用于游戏的不错的读物。
函数反应式编程是一个单独的主题,它与它有点不同。 这是 Yan Cui的有关在ELM中编写简单游戏的话题,您可能也会觉得很有趣。
作为一个非常简单的示例,您可以执行以下操作
// 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});
}
另请参阅State Monad
这是他们文档中的Haskell示例
一个简单的示例,演示了标准
Control.Monad.State monad
的用法。 这是一个简单的字符串解析算法。
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
您的示例以在游戏对象之外存在某种全局游戏数据为前提。 这与功能编程完全相反。 “修复”使您的示例变得毫无趣味和缺乏信息性; 而且在许多方面还更好:
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
}
}
实际上,在OOP世界中,这可能也是一种更好的方法。 游戏状态是程序操作的状态,因此此更改的最后一刻将是简单地调用gameData.main()
而不是使外部程序了解其内部或状态更改的任何信息。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.