我目前正在寻找一种优雅且高效的解决方案来解决我认为非常常见的问题。请考虑以下情况:
我定义了一个基于BTree的文件格式,其定义(以简化的方式),如下所示:
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
函数并将更改写回树中结构体。问题是必须以某种方式保存FileOffsets。
我有两种解决这个问题的方法。他们都缺乏优雅和/或效率:
这种方法扩展了MemTree以包含偏移量:
data MemTree = MemNode [Key] [(MemTree, Maybe FileOffset)]
| MemNode [Key] [Data]
然后,读取函数将在FileTree
中读取,并将FileOffset
与MemTree
引用一起存储。写入将检查引用是否已经具有关联的偏移量,如果是,它只是使用它。
优点:易于实现,无需开销即可找到偏移量
缺点:公开用户的内部人员,负责将偏移设置为Nothing
解决此问题的另一种方法是阅读FileTree
并创建一个保留在StableName.Map
上的FileOffsets
。这样(如果我正确理解StableName
的语义),应该可以采用最终MemTree
并查找StableName
中每个节点的StableName.Map
。如果有一个条目,则该节点是干净的,不必再次写入。
优点:不会将内部公开给用户
缺点:涉及地图中查找的开销
这是我能想到的两种方法。第一个应该更有效率,第二个更令人愉快。我希望你对我的想法发表评论,也许有人甚至会考虑更好的方法?
我正在寻找这样的解决方案有两个原因:
一方面,您应该尝试通过使用类型系统来处理错误。前面提到的用户当然是系统中下一层(即我)的设计者。通过处理纯树表示,将无法发生某些类型的错误。对文件中树的所有更改都应位于一个位置。这应该使推理更容易。
另一方面,我可以实现像insert :: FilePath -> Key -> Value -> IO ()
之类的东西,并完成它。但是当我通过更新树来保存(一种)日志时,我将失去一个非常好的特性。事务(即合并多个插入)只是在内存中处理同一个树并将差异写回文件。
答案 0 :(得分:2)
我认为包Data.Generic.Diff可能完全符合您的要求。它引用了某人的论文,以了解其工作原理。
答案 1 :(得分:1)
我是Haskell的新手所以我不会展示代码,但希望我的解释可能有助于解决问题。
首先,为什么不将MemTree仅暴露给用户,因为这是他们将要更新的内容,并且FileTree可以完全隐藏。这样,稍后,如果您想将其更改为转到数据库,例如,用户看不到任何差异。
因此,由于FileTree是隐藏的,为什么不在你想要更新时读取它,然后你有偏移量,所以更新,然后再次关闭文件。
保持偏移的一个问题是它阻止了另一个程序对文件进行任何更改,并且在您的情况下可能没问题,但我认为通常这是一个糟糕的设计。
我看到的主要变化是MemTree不应该是懒惰的,因为文件不会保持打开状态。