1

我寫在Haskell一個遊戲程序,目前有這樣的哈斯克爾最有效的可變數據結構

data World = World { 
    worldPlayer :: !(IORef GameObject), 
    worldEntities :: ![IORef GameObject], 
    ... 
} 

每次更新數據類型,下面的更新被寫入玩家IORef

updatePlayer :: GameObject -> [IORef GameObject] -> IO GameObject 

在此功能中,它檢查每個物體的碰撞,然後移動播放器。 但我想updatePlayer函數是純的,所以我需要使用不同的數據結構。

最明顯的想法是從世界上採取[IORef GameObject],並通過在每個索引上調用readIORef將其轉換爲IO [GameObject]。但是這將是非常低效的。

另一種可能的方法,我發現這樣做是使用Data.Vector.MVectorData.Vector.Generic.unsafeUnfreezeunsafeFreeze,其中有O(1)性能做worldEntities :: !(MVector (PrimState IO) GameObject)。問題是unsafeUnfreezeunsafeFreeze只適用於某些數據類型。

我還發現IOArray,所以我可以使用IOArray Int GameObject,但我找不到將IOArray轉換爲不可變結構的方法。

最後,我可以做IORef [GameObject]IORef (Vector GameObject),但我不確定這將是多高效。

什麼是最有效的方法來做到這一點?

+2

實際上您是否需要可變數據結構?你確定每次更新都不夠快返回一個新的數據結構嗎? –

+0

@TomEllis我認爲這將是非常低效的,如果你的意思是計算整個'世界'每一次更新。 – functorial

+2

這取決於GameObject是什麼。返回一個新的對象肯定會比變異效率低,但可能不如你想象的那樣多,而且它確實容易得多!如果更新之間幾乎沒有變化,並且可以共享很多舊結構,它實際上可以非常快速。 –

回答

5

您可以使用lenses而不是可變對象來獲得「類似setter」的行爲。嘗試在使用可變狀態之前進行嘗試,這在Haskell中非常難看(故意醜陋,阻止你這麼做)。 (編輯後添加:「類似setter」的語法。「setters」仍然創建新的對「set」結果的引用,所以你仍然需要對你的主循環進行排序,以便從setter的返回值中讀取,當然,您不能重新讀取舊的(不可變的)引用以獲取更新後的值。)

+0

Haskell故意讓事情變得像可變狀態醜陋以阻止它們的使用,這絕對不是真的;那將是一個糟糕的語言決定。 Haskell中的可變狀態從純代碼中適當地劃分出來,是的,但是ST monad代碼實際上並不比任何其他命令式語言醜陋(儘管我猜這是一個非常主觀的話題)。 – trolox