盛大に遅れました…
最近思いついたネタで実用性の高そうなものを紹介。
binaryやcerealのようなライブラリはデータを密にシリアライズするが、その際にフィールド名や型などの情報は失われてしまう。かといってそれらを一つ一つすべて含めるとひどく効率の悪いフォーマットになってしまう。そこで、スキーマを分離できるような仕組みを作れないかと考えて作ったのがこのクラスだ。
{-# LANGUAGE TypeFamilies, ScopedTypeVariables, FlexibleContexts, UndecidableInstances #-} import Data.Binary class HasSchema a where type Schema a :: * toSerializer :: a -> Put toDeserializer :: Schema a -> Get a
HasSchema
クラスは、普通にシリアライズするメソッドtoSerializer
と、スキーマを使ってデシリアライズするtoDeserializer
を持っている。Schemaは型族なのでどんなものでも使える(一般にどうあるべきかよく考えていない)。
instance HasSchema a => HasSchema [a] where type Schema [a] = [Schema a] toSerializer = mapM toSerializer toDeserializer = mapM toDeserializer
同じスキーマの値が連続している場合、以下のTable
という型を使うと無駄がない。
newtype Table a = Table (V.Vector a) instance HasSchema a => HasSchema (Table a) where type Schema (Table a) = Schema a toSerializer (Table v) = put (V.length v) >> mapM toSerializer v toDeserializer sch = get >>= \n -> Table <$> V.fromListN n <$> replicateM n (toDeserializer sch)
このSchematic
というデータ型はスキーマとデータの対にすぎないが、これで包んでおくことで簡単にHasSchemaの力を引き出せる。
data Schematic a = Schematic !(Schema a) !a instance (HasSchema a, Binary (Schema a)) => Binary (Schematic a) where get = get >>= \sch -> Schematic sch <$> toDeserializer sch put (Schematic sch a) = put sch >> toSerializer a
今のところ、社内で使っているレコード型を簡単にシリアライズするために実験的に使っているが、より一般的で面白い使い方があるかもしれない。この記事では、実体のあるHasSchema
のインスタンス(instance HasSchema Int
など)は一切定義されていない。どう作っていくかは、これからの課題だ。