我正在尝试使用read和show进行序列化/反序列化 (这本身不是问题),但在数据意义上是可扩展的 类型可以扩展(但不缩小)。
假设我有这种类型:
data Foo = { bar :: Int } deriving (Show, Read)
列表:
foos = [Foo 1, Foo 2]
我可以轻松地将其反序列化为文件:
hPutStrLn fileHand . ppShow $ foos
然后我可以将它序列化:
!str <- hGetContents fileHand
let foosFromFile = fromMaybe [] $ (readMaybe :: String -> Maybe [Foo]) str
但是假设几个月后我想在Foo中添加一个'baz'字段 类型。旧格式文件中的直接序列化将不再存在 使用read,我将需要转换文件(我不是真的 要)。
那么,是否有优雅(没有在程序本身中放置明确的版本控制逻辑)解决方案仍然序列化文件中的数据,并使用默认值填充缺少的字段?也许某些类型的技巧?
感谢。
答案 0 :(得分:10)
这可能不是你想要的,因为你想要避免显式版本控制,但我仍然想指出safecopy
这是版本序列化的首选解决方案,至少让它变得有点无痛
在支持添加任意数量的新字段时,我认为没有办法使用默认的Show
和Read
实例,但您当然可以编写自己的Read
手工处理缺失记录字段的实例。但是,我认为这比使用safecopy
更加费力和容易出错。
答案 1 :(得分:4)
根据您的使用情况,您还可以使用Yesod中的persistent将数据保存在数据库中。引用:
在这些情况下,Persistent遵循类型安全和简洁,声明性语法的指导原则。其他一些不错的功能是:
- 数据库无关。 PostgreSQL,SQLite,MySQL和MongoDB都有一流的支持,并且正在开发实验性的CouchDB支持。
- 由于本质上是非关系型的,我们同时能够支持更多的存储层,并且不会受到通过连接产生的一些性能瓶颈的限制。
- 处理SQL数据库的一个主要障碍是对架构的更改。 Persistent可以自动执行数据库迁移。
持久会为您处理数据更改:
对于以下情况,它将自动更改架构:
- 字段的数据类型已更改。但是,如果无法翻译数据,数据库可能会反对此修改。
- 添加了一个字段。但是,如果该字段不为null,则不提供默认值(稍后我们将讨论默认值)并且数据库中已有数据,数据库不会允许这种情况发生。
- 字段从非null转换为null。在相反的情况下,Persistent将根据数据库的批准尝试转换。
- 添加了一个全新的实体。
答案 2 :(得分:3)
希望能够在仍然能够访问您的内容的同时更改数据布局几乎是 定义发明数据库管理系统的动机之一。您是否考虑过将数据放入简单的SQLite表中?对于你正在尝试做的事情可能有些过分,但它有一些优点:
*
作为列选择器,那么旧代码仍然可以读取由较新版本的应用程序创建的内容。 (即,转发兼容性以及向后)。我不知道这是否适合您的情况,但值得考虑。
答案 3 :(得分:2)
是。只需添加一个多态字段:
data Foo a = { bar :: Int, extra :: a } deriving (Show, Read)
然后使用a
必须可序列化的约束来定义序列化实例:
instance (Serialize a) => Serialize (Foo a) where ...
当您不使用额外字段时,只需在其中插入()
,因为()
可以轻松序列化(并且已经有Serialize
个实例)。
编辑:糟糕,刚刚意识到你在谈论漂亮的印刷。等效的解决方案是定义一个类型类:
class PrettyPrint a where
pp :: a -> String
instance PrettyPrint () where
pp () = ""
instance (PrettyPrint a) => PrettyPrint (Foo a) where
pp = ... -- You fill this in