Haskell 如何创建两个复杂数据结构的差异? 问题说明:

Haskell 如何创建两个复杂数据结构的差异? 问题说明:,haskell,data-structures,Haskell,Data Structures,我目前正在寻找一个优雅而有效的解决方案,我想这是一个很常见的问题。考虑以下情况: 我根据定义如下(以简化的方式)的定义定义了一种文件格式: data FileTree = FileNode [Key] [FileOffset] | FileLeaf [Key] [Data] 将其从文件读写到惰性数据结构是可以实现的,并且工作正常。这将导致出现以下情况: data MemTree = MemNode [Key] [MemTree] | Mem

我目前正在寻找一个优雅而有效的解决方案,我想这是一个很常见的问题。考虑以下情况:

我根据定义如下(以简化的方式)的定义定义了一种文件格式:

data FileTree = FileNode [Key] [FileOffset]
              | FileLeaf [Key] [Data]
将其从文件读写到惰性数据结构是可以实现的,并且工作正常。这将导致出现以下情况:

data MemTree = MemNode [Key] [MemTree]
             | MemLeaf [Key] [Data]
现在,我的目标是拥有一个通用函数
updateFile::FilePath->(MemTree->MemTree)->IO()
,它将读取
FileTree
并将其转换为MemTree,应用
MemTree->MemTree
函数并将更改写回树结构。问题是文件偏移量必须以某种方式保存

我有两种方法来解决这个问题。两者都缺乏优雅和/或效率:

方法1:扩展MemTree以包含偏移 此方法扩展MemTree以包含偏移:

data MemTree = MemNode [Key] [(MemTree, Maybe FileOffset)]
             | MemNode [Key] [Data]
然后,read函数将读取
FileTree
,并将
FileOffset
存储在
MemTree
引用旁边。写入将检查引用是否已经有关联的偏移量,如果有,则只使用它

优点:易于实现,无需开销即可找到偏移量

缺点:向负责将偏移设置为
Nothing

方法2:在二级结构中存储偏移 解决此问题的另一种方法是读入
文件树
,并创建一个
StableName.Map
,它保存在
文件偏移量
上。这样(如果我正确理解了
StableName
的语义),应该可以获取最后的
MemTree
并在
StableName.Map
中查找每个节点的
StableName
。如果存在条目,则节点是干净的,无需再次写入

优点:不会向用户公开内部构件

缺点:涉及在地图中查找的开销

结论 这是我能想到的两种方法。第一个应该更有效,第二个更好看。我想听听你对我的想法的评论,也许有人有更好的想法

[编辑]合理的 我寻找这样的解决方案有两个原因:

data FileTree = FileNode [Key] [FileOffset]
              | FileLeaf [Key] [Data]
一方面,您应该尝试使用类型系统在错误出现之前处理错误。前面提到的用户当然是系统下一层(即me)的设计者。通过使用纯树表示法,某些种类的错误将不会发生。对文件中树的所有更改都应放在一个位置。这将使推理更容易


另一方面,我可以实现像
insert::FilePath->Key->Value->IO()
这样的东西,然后完成它。但是我会失去一个非常好的特性,当我通过更新树来保存一个日志时,它是免费的。事务(即多个插入的合并)只是在内存中的同一棵树上工作,并将差异写回文件。

我是Haskell的新手,所以我不会显示代码,但希望我的解释能帮助解决问题

首先,为什么不向用户仅公开MemTree,因为这是他们将要更新的内容,而FileTree可以完全隐藏。这样,稍后,如果您想将其更改为数据库,例如,用户看不到任何差异

因此,既然文件树是隐藏的,为什么不在更新时读入它,然后就有了偏移量,更新后再关闭文件呢

保留偏移量的一个问题是,它会阻止另一个程序对文件进行任何更改,在您的情况下,这可能很好,但我认为作为一般规则,它是一个糟糕的设计


我看到的主要变化是MemTree不应该是懒惰的,因为文件不会保持打开状态。

我认为包可能会完全满足您的需要。它引用了某人的论文来了解它是如何工作的。

为什么要向用户公开
MemTree
类型?你不能保持内部私有,并公开你想要的任何接口吗?这不是一个真正合适的解决方案,但这个问题促使你对第一种方法的类型强制版本进行了一些修补,即用户必须处理事情的地方(如果他们不处理的话,就会出现编译器错误)。如果你好奇的话。但结果是朝着一个稍微不同的方向走去,所以在这种情况下可能对你没有帮助。@AndrewC:也许你是对的。我想我是在过度设计一个小问题。我希望有一个很酷的解决方案来隐藏每个节点的附加信息。在某种程度上,比如State Monad在不需要状态时“隐藏”状态。这样打字系统就可以防止我犯愚蠢的错误。但是如果没有办法做到这一点,我会用简单的方法。monad可以隐藏东西,因为它们使用标准的接口(和语法)来完成所有的工作。您可以使用monad隐藏附加信息,但这可能不是树的最佳接口!为什么不推出自己的界面,将管道隐藏在引擎盖下?可能有一个public
MemTree
和private
MemTreeAnnotated
insert
应该秘密地为FileOffset添加
Nothing
leftSubTree
righsubtree
应该提供没有FileOffset注释的数据,但是如果FileOffset在节点中计数,
fmap
可以保留它
delete
应该保留它。@Florian:与
State
的工作方式最接近的等效方式可能是要求用户构造一个具体的更新函数,拉链式,而不是直接公开
MemTree
。如果用户具有
MemTree
构造函数,则会出现冗长的结构