我正在研究一些与数据库模式接口的代码,该数据库模式为持久性图形建模.在我详细讨论我的具体问题之前,我认为可能有助于提供一些动力.我的架构围绕书籍,人物和作者角色.一本书有许多作者角色,每个角色都有一个人.但是,您必须创建新书并对新版本进行修改,而不是允许对书籍对象进行直接UPDATE查询.
现在,回到Haskell的土地上.我目前正在使用几个类型类,但重要的是我有HasRoles和Entity:
class HasRoles a where
-- Get all roles for a specific 'a'
getRoles :: a -> IO [Role]
class Entity a where
-- Update an entity with a new entity. Return the new entity.
update :: a -> a -> IO a
Run Code Online (Sandbox Code Playgroud)
这是我的问题.在更新图书时,您需要创建新图书版本,但还需要复制以前的图书角色(否则会丢失数据).最简单的方法是:
instance Entity Book where
update orig newV = insertVersion V >>= copyBookRoles orig
Run Code Online (Sandbox Code Playgroud)
这是好的,但有一些困扰我,这就是缺乏不变,如果事情是任何担保Entity 和 HasRoles,然后插入一个新的版本将复制在现有的角色.我想到了两个选择:
一个'解决方案'是介绍RequiresMoreWork a b.从上面开始,insertVersion现在返回一个HasRoles w => …
我正在尝试创建一个由几种不同类型组成的游戏引擎:
data Camera = Camera ...
data Light = SpotLight ... | DirectionalLight ...
data Object = Monster ... | Player ... | NPC ...
Run Code Online (Sandbox Code Playgroud)
但是,我现在正在尝试为所有这些实体实现基本物理.这要求它们各自包含a pos :: (Double, Double, Double)和a velocity :: (Double, Double, Double).
在面向对象的语言中,我将它实现为:
Camera implements PhysicalObject
Run Code Online (Sandbox Code Playgroud)
其中PhysicalObject包含两个属性pos和velocity.
我的直接反应是将它们全部放在同一类型中:
data Object = Monster ... | Player ... | NPC ... | Camera ...
Run Code Online (Sandbox Code Playgroud)
然而,我担心这可能会使得实现特定于摄像机的功能,特定于灯光的功能等变得困难.实际上,除了它们在世界上都具有物理位置和速度这一事实之外,它们几乎没有其他共同点. .
有没有比在每个类型构造函数中定义两个属性更简单的方法?
假设我有一个记录,例如Person,我希望能够通过多个数据结构查看此人.也许有一个名字的索引,一个人的邮政编码的另一个索引,以及该人当前纬度和经度的另一个索引.也许还有更多的数据结构.所有这些都存在,因为我需要有效地查找具有不同标准的人.
如果我只需要阅读一个人的属性,这是没有问题的.但现在假设我需要使用其中一个数据结构查找一个人,然后更新该人的数据.
在OOP语言中,每个数据结构都指向内存中的同一个人.因此,当您更新一个时,您也隐式更新其他数据结构的引用.这几乎是副作用和杂质的定义.我知道这完全违背了Haskell范式,我并不期望Haskell以这种方式工作.
那么,什么是Haskell-ish方法呢?要清楚,问题是这样的:我通过一个数据结构查找一个人,并将该人(可能还有一些其他任意数据)传递给一个类型的函数ArbitraryData -> Person -> Person.如何在所有各种查找结构中传播此更改?
作为Haskell的相对新人,我的第一直觉是每次更新一个人时,都会使用新更新的人重建每个查找结构.但这似乎很多仪式,我有充足的机会搞砸了GHC无法察觉的方式,而且一点也不优雅.Haskell以其优雅而闻名,我无法想象它缺乏这种常见和基本问题的优雅解决方案.所以我想我错过了一些东西.
作为参考,这个问题扩展了我在以下问题中讨论的一些问题:
编辑
我想到的一个解决方案:不要在主状态中维护每个查找结构的副本.只保留一个存在的所有人的列表,这是我们更新人员时唯一需要更新的事项.每次你需要通过邮政编码进行查询时,将所有人员的列表传递给一个生成有效的by-zip-code数据结构的函数.然后对结果执行查找.
我不知道这是否有效.如果它导致CPU在每次使用时实际重新计算查找结构,则这是不可接受的.但我知道Haskell有时可以避免重新评估相同的表达式.不幸的是,在这种情况下我仍然没有想到.所以我不知道这种方法是否可行.
换句话说:我可以编写我的函数,就好像他们每次都在计算查询一样,实际上GHC会在底层数据没有改变的情况下优化它吗?因为那将是我上面提到的问题的一个非常优雅的解决方案.