如何在Haskell中以组合方式处理数据结构迁移?

时间:2017-02-11 18:35:11

标签: haskell serialization types

我试图以这样的方式实现(de)Haskell中数据结构的序列化:

  1. 负责数据结构的架构演变,
  2. 允许安全阅读过去的版本,如果提供一些"修补"代码存在,
  3. 不需要保留旧版本的数据类型定义(如safecopy
  4. 的情况

    我过去以一种我觉得不满意的方式实现了这种机制:代码中有很多重复,因为它需要遍历整个结构,即使只有一个叶子发生变化,处理各种版本的代码也不可组合。

    我正在寻找的东西就像VCS中的补丁流一样:在每次版本更改时,我只需编写代码来处理特定的更改(例如,某些字段从{{1}转换而来} Text,有一个新字段,一些字段被删除...)并且在已知版本中给出了一些序列化的字节块,Int函数应用所有补丁来检索有效的数据结构

    我试图按照这些方式编写一些代码,但是无法以我正在寻找的方式获得可组合的东西(我甚至没有解决和类型的问题......)。这是我的尝试:

    load

    我们的想法是以一种可以应用最小变化的方式来实现一个应用结构。

    这似乎只能使用某种形式的data Versioned (v :: Nat) a where (:$:) :: (a -> b) -> Versioned v a -> Versioned v b (:*:) :: Versioned v (a -> b) -> Versioned v a -> Versioned v b Atom :: Get a -> Versioned v a Cast :: Versioned v' a -> Versioned v a 反序列化机制:将字节反序列化为通用形式,然后应用变换器链来达到满足当前的形状。

    任何解决方案的提示都会非常有用。

    2017年2月13日

    我的问题可分为两个子问题:

    1. 如何确保静态存在每个版本的反序列化功能,直到某些(静态)已知版本?
    2. 如何以安全和微创的方式处理数据结构的迁移?
    3. 问题1.产生以下(非编译代码):

      Generic

      问题是, -- | A class instantiating a serializer/deserializer for some version class Versionable (v :: Nat) a where reader :: Proxy v -> Get a writer :: Proxy v -> a -> Put -- | Current version is a "global" constraint type family CurrentVersion :: Nat class VersionUpTo (v :: Nat) a instance (Versionable 1 a) => VersionUpTo 1 a instance (Versionable v a, VersionUpTo (v - 1) a) => VersionUpTo v a load :: (VersionUpTo CurrentVersion a) => ByteString -> Either String [a] load = runGet loadGetter where loadGetter = sequence $ repeat $ do v <- getInt32be case v of 1 -> reader (Proxy :: Proxy 1) 2 -> reader (Proxy :: Proxy 2) 3 -> reader (Proxy :: Proxy 3) 发送的价值当然取决于v,这引发了以下问题:

      1. 如何编写通用CurrentVersion a函数,该函数将从基础字节流中读取版本并调度到正确的读取器函数,而不需要明确枚举所有情况?
      2. 即使loadCurrentVersion的呼叫站点处不是静态的,但在定义站点处不知道,因此无法枚举所有有效案例。似乎唯一的选择是以某种方式使用TH ...

        生成案例

        问题2.与1正交。这里的问题是类型load的数据结构随着时间的推移而发展,但我们需要处理旧的表示:我们应该能够反序列化任何版本{{1} } T直到v。这可以通过为每个目标版本定义T来轻松完成,但由于版本CurrentVersionVersionable n T之间的更改通常仅限于结构的一部分,因此会引入大量冗余。

        我认为补丁流的比喻不起作用,因为它实际上是倒退的:起点是当前的数据结构,我们需要使过去的表示适应当前的版本。以下是同一对象的3个版本:

        n

        我们看到每个过去版本都是当前版本的改编版本,因此有一些规律性。

        因此,将n+1表示为具体化的 instance Versionable 1 Obj3 where reader _ = doGet $ Obj3 :$: (fromInt :$: getint) :*: (fromText :$: Atom get) instance Versionable 2 Obj3 where reader _ = doGet $ Obj3 :$: Atom get :*: (fromText :$: Atom get) instance Versionable 3 Obj3 where reader _ = doGet $ Obj3 :$: Atom get :*: getf2 F2 writer _ Obj3{..} = put f31 >> put f32 (或者可能是reader ic)仿函数的想法可以应用于手术更新以应对旧版本。但后来我不知道如何在当前解串器树的深处选择一些节点,以便以类型安全的方式应用一些变化......

        2017年2月13日

        上面的第2点似乎只会导致错综复杂的代码,涉及很多类型级的魔法以获得微小的好处。考虑前面提到的Applicative的3个版本,理想情况下我想找到一种写作方式:

        Monad

        其中Obj3表示我们希望使用第二个参数替换 geto3 = Obj3 :$: Atom get :*: getf2 F2 instance Versionable 2 Obj3 where reader _ = doGet $ _replaceAt (0,1) (fromText :$: Atom get) get03 instance Versionable 3 Obj3 where reader _ = doGet $ get03 writer _ Obj3{..} = put f31 >> put f32 的反序列化中索引为_replaceAt :: (Int, Int) -> Versioned a -> Versioned b -> Versioned b的某个子树,其类型为(x,y)。似乎可以以类型安全的方式表达,但这需要在b中将Versioned a的结构公开为Obj3类型。

0 个答案:

没有答案