假设您正在Haskell中构建一个相当大的模拟.有许多不同类型的实体,其属性随着模拟的进行而更新.让我们说,为了举例,您的实体被称为猴子,大象,熊等.
维护这些实体状态的首选方法是什么?
我想到的第一个也是最明显的方法是:
mainLoop :: [Monkey] -> [Elephant] -> [Bear] -> String
mainLoop monkeys elephants bears =
let monkeys' = updateMonkeys monkeys
elephants' = updateElephants elephants
bears' = updateBears bears
in
if shouldExit monkeys elephants bears then "Done" else
mainLoop monkeys' elephants' bears'
Run Code Online (Sandbox Code Playgroud)
在mainLoop
函数签名中明确提到的每种类型的实体已经很难看了.你可以想象,如果你有20种类型的实体,它会变得非常糟糕.(20对于复杂的模拟来说并非不合理.)所以我认为这是一种不可接受的方法.但它的优点在于它们的功能updateMonkeys
非常明确:它们会获取Monkeys列表并返回一个新的.
那么接下来的想法是将所有内容都卷成一个包含所有状态的大数据结构,从而清理以下签名mainLoop
:
mainLoop :: GameState -> String
mainLoop gs0 =
let gs1 = updateMonkeys gs0
gs2 = updateElephants gs1
gs3 = updateBears gs2
in
if shouldExit gs0 then "Done" else
mainLoop …
Run Code Online (Sandbox Code Playgroud) 我正在努力在Haskell中实现UCT算法,这需要大量的数据杂耍.没有太多细节,它是一个模拟算法,在每个"步骤",基于一些统计属性选择搜索树中的叶节点,在该叶子上构造新的子节点,并且对应于新叶及其所有祖先都会更新.
鉴于所有这些杂耍,我并不是非常敏锐,无法弄清楚如何使整个搜索树成为Okasaki的一个不可改变的数据结构.相反,我一直在玩ST
monad,创建由可变STRef
s 组成的结构.一个人为的例子(与UCT无关):
import Control.Monad
import Control.Monad.ST
import Data.STRef
data STRefPair s a b = STRefPair { left :: STRef s a, right :: STRef s b }
mkStRefPair :: a -> b -> ST s (STRefPair s a b)
mkStRefPair a b = do
a' <- newSTRef a
b' <- newSTRef b
return $ STRefPair a' b'
derp :: (Num a, Num b) => STRefPair s a b -> ST s …
Run Code Online (Sandbox Code Playgroud) 我有一个包含一些数据的文件.这些数据永远不会改变,我想让它在IO monad之外可用.我怎样才能做到这一点?
示例(请注意,这只是一个示例,我的数据不可计算):
primes.txt:
2 3 5 7 13
code.hs:
primes :: [Int]
primes = map read . words . unsafePerformIO . readFile $ "primes.txt"
Run Code Online (Sandbox Code Playgroud)
这是"合法"使用unsafePerformIO
吗?还有替代品吗?
假设我有一个记录,例如Person
,我希望能够通过多个数据结构查看此人.也许有一个名字的索引,一个人的邮政编码的另一个索引,以及该人当前纬度和经度的另一个索引.也许还有更多的数据结构.所有这些都存在,因为我需要有效地查找具有不同标准的人.
如果我只需要阅读一个人的属性,这是没有问题的.但现在假设我需要使用其中一个数据结构查找一个人,然后更新该人的数据.
在OOP语言中,每个数据结构都指向内存中的同一个人.因此,当您更新一个时,您也隐式更新其他数据结构的引用.这几乎是副作用和杂质的定义.我知道这完全违背了Haskell范式,我并不期望Haskell以这种方式工作.
那么,什么是Haskell-ish方法呢?要清楚,问题是这样的:我通过一个数据结构查找一个人,并将该人(可能还有一些其他任意数据)传递给一个类型的函数ArbitraryData -> Person -> Person
.如何在所有各种查找结构中传播此更改?
作为Haskell的相对新人,我的第一直觉是每次更新一个人时,都会使用新更新的人重建每个查找结构.但这似乎很多仪式,我有充足的机会搞砸了GHC无法察觉的方式,而且一点也不优雅.Haskell以其优雅而闻名,我无法想象它缺乏这种常见和基本问题的优雅解决方案.所以我想我错过了一些东西.
作为参考,这个问题扩展了我在以下问题中讨论的一些问题:
编辑
我想到的一个解决方案:不要在主状态中维护每个查找结构的副本.只保留一个存在的所有人的列表,这是我们更新人员时唯一需要更新的事项.每次你需要通过邮政编码进行查询时,将所有人员的列表传递给一个生成有效的by-zip-code数据结构的函数.然后对结果执行查找.
我不知道这是否有效.如果它导致CPU在每次使用时实际重新计算查找结构,则这是不可接受的.但我知道Haskell有时可以避免重新评估相同的表达式.不幸的是,在这种情况下我仍然没有想到.所以我不知道这种方法是否可行.
换句话说:我可以编写我的函数,就好像他们每次都在计算查询一样,实际上GHC会在底层数据没有改变的情况下优化它吗?因为那将是我上面提到的问题的一个非常优雅的解决方案.