我刚从OO背景开始涉足功能范例。我很困惑如何在函数式编程语言中的函数调用之间保持状态。
考虑一个您正在创建的游戏示例,您打算将其作为库发布以供其他人使用。其他人将使用您的游戏代码作为他们游戏的领域,并将其连接到他们自己的UI。使用OO方法,人们可能希望客户端使用您的游戏代码:
->
这里,游戏类型的内部数据结构对客户端是隐藏的,游戏类型可以暴露公共API,它确切地定义客户端如何与游戏交互。由于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的某些功能,或仅展示游戏数据结构中的某些数据?
答案 0 :(得分:2)
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
或{{1}干扰那个。
至于数据隐藏,这通常不是FP中的一个问题 - 因为状态不可变,并且函数所做的所有更改在返回值中都是明确的,因此不太担心提供访问权限向您的API用户提供数据。它不像他们可以通过随意改变它来破坏内部的东西。然而,您可以将该州分成两个独立的部分,并且只传入" public"一个更新功能(通过改为draw
)。
虽然上面的例子相当简单,但它表明,就表现力而言,你可以用FP语言编写游戏。 Here's关于在游戏中应用类似功能方法的精彩内容。
一个单独的主题是功能性反应式编程,它有一点不同的味道。 Here's Yan Cui谈到在ELM中编写一个简单的游戏,你可能也会感兴趣。
答案 1 :(得分:1)
作为一个非常简单的例子,您可以执行类似
的操作// 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});
}
这是他们的文档中的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
答案 2 :(得分:0)
您的示例预先假定游戏对象之外存在某种全局游戏数据。这与函数式编程非常相反。 “修复”它使你的例子非常无趣且无法提供信息;而且,在许多方面,更好:
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()
,而不是让外部程序知道有关其内部或状态更改的任何信息。 / p>