ins*_*itu 5 serialization haskell types
我试图在以下方式中实现(de)Haskell中数据结构的序列化:
我过去以一种我发现不满意的方式实现了这种机制:代码中有很多重复,因为它需要遍历整个结构,即使只有一个叶子发生变化,处理各种版本的代码也是不可组合的.
我正在寻找的东西就像VCS中的补丁流一样:在每次版本更改时,我只需要编写代码来处理特定的更改(例如,某些字段从转换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
Run Code Online (Sandbox Code Playgroud)
我们的想法是以一种可以应用最小变化的方式来实现一个应用程序结构.
这似乎只能使用某种形式的Generic
反序列化机制:将字节反序列化为通用形式,然后应用变换器链来达到满足当前的形状.
对解决方案的任何暗示都是最有帮助的.
2017年2月13日
我的问题可以分为两个子问题:
问题1.产生以下(非编译代码):
-- | 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)
Run Code Online (Sandbox Code Playgroud)
问题是,当然,v
派遣的价值取决于CurrentVersion a
,这引发了以下问题:
load
函数,它将从底层字节流中读取版本并调度到正确的读取器函数,而不需要明确枚举所有情况?即使CurrentVersion
在呼叫站点没有静态,load
在定义站点也不知道,因此无法枚举所有有效案例.似乎唯一的选择是以某种方式使用TH生成案例...
问题2.正交1.这里的问题是,数据结构类型T
随着时间的演进,但我们需要照顾的老表示的:我们应该能够反序列化任何版本v
的T
到CurrentVersion
.这可以通过Versionable n T
为每个目标版本定义a 来轻松完成,但是由于版本之间的变化,这会引入大量冗余n
,n+1
并且通常仅限于结构的一部分.
我认为补丁流的比喻不起作用,因为它实际上是倒退的:起点是当前的数据结构,我们需要使过去的表示适应当前版本.以下是同一对象的3个版本:
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
Run Code Online (Sandbox Code Playgroud)
我们看到每个过去的版本都是对当前版本的改编,因此有一些规律性.
因此,表示reader
作为一个具体化Applicative
(或可能是Monad
ic)仿函数的想法可以应用于手术更新以应对旧版本.但后来我不知道如何在当前解串器树的深处选择一些节点,以便以类型安全的方式应用一些变化......
2017年2月13日
上面的第2点似乎只会导致复杂的代码,涉及很多类型级的魔法,以获得微小的好处.考虑前面提到的3个版本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
Run Code Online (Sandbox Code Playgroud)
where _replaceAt :: (Int, Int) -> Versioned a -> Versioned b -> Versioned b
意味着我们想要(x,y)
在反序列化器中用索引替换某个子树b
,其类型是Versioned a
第二个参数.这似乎是可行的表达,在类型安全的方式,但这需要暴露的结构Obj3
作为一个类型T
的Versioned T
.