问题描述
为了我的功能编程能力,我正在尝试使用F#构建命令行游戏。借助结构简单的游戏,我可以保持游戏的纯度,例如:
(未经严格测试)
type HangmanGame = HangmanGame [char] string (deriving Show)
runGame :: Game -> IO Game
runGame (Game guessed answer) = do
guess <- getLine
-- validate guess
-- do someting with guess
runGame (Game guess:guessed answer)
我认为这个简单的类似子手的游戏的主要概念是Game guessed answer
已经消失,取而代之的是Game guess:guessed answer
。由于游戏只有两个领域,因此以新的价值进行重构非常容易。但是当涉及到...
type ComplicatedGame = Game {
a :: Int,b :: String,c :: Int,d :: String,...
z :: String
}
当我只想更改c
时,我必须像Game a b newC d ... z
那样重建整个游戏价值。
此外,借助类似《太空漫游》游戏系统
type Galaxy = Galaxy {
name :: String,starSystems :: [StarSystem]
}
type StarSystem = StarSystem {
name :: String,planets :: Planets
}
type Planet = Planet {
name :: String,men :: [Human],buildings :: [Building]
}
在单个行星中对men
的数量的一次更改将导致Galaxy
的整个重建!
由于稍后将使用Unity3d和C#创建UI系统,因此我想使用F#创建核心代码。但是我认为禁止变异将导致上述情况,这不是理想的情况。但我也认为,坚持使用C#会比采用可变性更好,因为可变性不会增加我的函数式编程技能。
请您给我一些建议,以通过函数式编程来创建和处理巨大的数据结构(例如上面的Galaxy
示例)。
解决方法
如果您只想替换c
值中的ComplicatedGame
(我们将其称为cg
),则可以使用copy-and-update expression:
let c1 = { cg with c = newC }
如果您愿意,可以嵌套这些,尽管确实很尴尬:
let g1 = { g with starSystems = { (* update one planet here...*) } }
(我没有尝试进行编译,因此我可能在上面做了一些错别字。)
在Haskell中不得不编写嵌套的复制和更新表达式的尴尬导致lenses,但是由于F#不支持类型类,因此F#中没有内置的等效项。您可以使用F#手工制作镜头,但它们不如Haskell那样好。
使用复制和更新表达式时,不重建整个Universe。值是不可变的,因此可以重用您不接触的数据结构。它非常有效,但不如突变有效(尽管编译器的优化可能会达到目标)。