【问题标题】:Normalized and immutable data model标准化和不可变数据模型
【发布时间】:2026-02-05 00:20:04
【问题描述】:

Haskell 如何解决“标准化不可变数据结构”问题?

例如,让我们考虑一个表示前女友/男友的数据结构:

data Man = Man {name ::String, exes::[Woman]}

data Woman = Woman {name :: String, exes::[Man]}

如果一个女人改变了她的名字并且她曾经和 13 个男人在一起,会发生什么?那么所有 13 个人也应该“更新”(在 Haskell 意义上)?需要某种规范化来避免这些“更新”。

这是一个非常简单的例子,但是想象一个有 20 个实体的模型,它们之间有任意关系,那该怎么办呢?

用不可变语言表示复杂的规范化数据的推荐方法是什么?

例如,可以在here 找到一个 Scala 解决方案(参见下面的代码),它使用引用。在 Haskell 中可以做什么?

class RefTo[V](val target: ModelRO[V], val updated: V => AnyRef) {
  def apply() = target()
}

我想知道,如果更通用的解决方案(如上述解决方案(在 Scala 中))在 Haskell 中不起作用或者它们不是必需的?如果他们不工作,那为什么不呢?我试图在 Haskell 中搜索执行此操作的库,但它们似乎不存在。

换句话说,如果我想在 Haskell 中对规范化的 SQL 数据库进行建模(例如与 acid-state 一起使用),是否有一种通用的方法来描述外键?一般来说,我的意思是,不要按照下面 cmets 中 chepner 的建议手动编码 ID。

编辑:

但换句话说,是否有一个库(用于 Haskell 或 Scala)在内存中实现 SQL/关系数据库(可能还使用事件溯源来实现持久性),这样数据库是不可变的,并且大多数 SQL 操作(查询/join/insert/delete/etc.) 已实现并且类型安全?如果没有这样的图书馆,为什么不呢?这似乎是一个不错的主意。我应该如何创建这样的库?

编辑 2:

一些相关链接:

【问题讨论】:

  • 如果你有标准化的数据,你不会有data Man = Man {name :: String, exes :: [WomanID]},其中womanID是数据结构故事Woman值的索引(类似于Map WomanID Woman?如果你改变Woman 值的名称,这不会影响引用它的任何 Man 值;您只需更新 Map 中的单个值。
  • @jhegedus 目前的问题有点宽泛——这真的取决于具体情况。如果您不断更新男性和女性,您可能希望在状态单子中执行计算(状态是男性/女性的表格/地图)。如果您正在寻找更通用的图形结构的功能方法,请查看fgl。关于ID:在某些情况下您可以tie the knot(有时甚至使用Map),但通常您可能需要手动编码ID。
  • 一张表代表一个(n个应用)关系; FK 表示如果某个子元组在一个关系中,那么它在另一个关系中。 “用不可变语言表示复杂、规范化数据的方法”与“描述外键的一般方法”有什么关系?另外,为什么“不可变”?这似乎是一条红鲱鱼,因为(正如您引用“更新”所承认但没有解释的那样)问题与状态本身无关(实际上,更新异常的问题实际上只能在 mutable下出现> 语义),但在一定程度上减少了无效数据结构值。
  • 我认为这是一个很好的问题,很遗憾看到所有接近投票。我想你会发现这个博文系列(关于“Purely Functional Retrogames”)很有启发性:prog21.dadgum.com/23.html
  • 您的困难不是不变性。它缺少一个运算符,它给出的值看起来像另一个值,用新部件替换旧部件。例如,对于记录类型,作为字段的部分存在这样的运算符,并且'“更新”'很简单。例如,对于一个行表,许多行可能会改变。不管可变性。我们遍历一个表示来更新部分。在不变性下具有可以克隆但在可变性下可能克隆。 “规范化”在一般意义上是重组以减少行走和零件的数量。可变性与不变性不是问题。

标签: scala haskell normalization immutability database-normalization


【解决方案1】:

问题是您将数据和关系存储在同一类型中。要正常化,您需要分开。关系数据库 101.

newtype Id a = Id Int -- Type-safe ID.
data Person = Person { id :: Id Person, name :: String }
data Ex = Ex { personId :: Id Person, exId :: Id Person }

现在,如果一个人更改了他们的名字,只有一个 Person 值会受到影响。 Ex 条目不关心人名。

【讨论】:

  • 有趣的地方!确实是多对多的关系。然而,正如问题中提到的那样,我所追求的是“不按照下面 cmets 中 chepner 的建议对 ID 进行手工编码”。某种库可以执行您的建议,但会删除所有样板并添加对查询、连接和诸如此类的支持。基本上是一个 SQL 数据库,但不是使用 SQL 作为查询语言,而是使用 Haskell 来描述可以用 SQL 描述的事物。类型安全且不可变。
  • @jhegedus 这就是问题所在。你不能抽象掉外键;它是您的领域语义的一部分——即,它回答了“是什么让关系中的一个人独一无二地那个人?”这个问题。没有图书馆可以为您决定。
  • 所以基本上Ex对应一个需要明确指定的SQL数据库表。我明白了,但也许 SQL 可以被 Haskell 取代,完全类型安全,不可变。或不 ?我想知道为什么没有这样做? (不是绑定到数据库,而是实际的内存数据库,用 Haskell 编写)。我问这个是因为一个朋友正在用 Scala 编写一个(成功的)应用程序,并且这样做正是因为 1)没有数据重复(内存数据库),2)类型安全 3)Scala 比 SQL 更具表现力。因此,如果在 Scala 中这样做是值得的,为什么没有 Haskell 库。为此而存在?我想知道
  • @jhegedus 不确定,我无法验证 Scala 代码在做什么——您问题中的链接是 404ing。
  • @jhegedus 三件事。 (1) RefTo 类型仍然使用某些标识符作为基本引用。请注意,您必须手动告诉它如何通过 ID 唯一地获取产品。 (2) 我不相信二极管方法带来的附加价值。感觉我可以使用 FRP (/streaming) 保持不变性。 (3) Haskell 的人可能并没有敏锐地感觉到需要对标识符引用进行包装,因为镜头和语言级别的细节。手动操作可能更简单。
【解决方案2】:

项目M63 非常适合我正在寻找的close。它是用 Haskell 编写的。

Gabriel Gonzalez 的帖子“A very general API for relational joins”中概述了更轻量级的 Haskell 解决方案。

【讨论】:

  • 您能否举例说明如何将 Project M63 专门应用于您询问的模型?此外,带有“关闭”文本的链接似乎指向您个人 Gmail 收件箱中的邮件。 Gabriel Gonzalez 的连接技术非常酷——但它仍然没有抽象出 ID 管理?
  • 谢谢,我修复了链接。
  • 根据描述,我不知道如何使用 M36,但它似乎是我想要的。不可变的关系数据库。目前似乎没有 Scala 等价物(至少不是公共的)。或者也许我只是不知道?知道 Scala 是否有类似 M36 之类的东西会很有趣。