首页 > 解决方案 > 如何为此 GADT 编写序列化实例?

问题描述

下面是一个尝试Serialize为简单 GADT 实现实例的模块。不幸的是,构造函数的get实现Reorder抱怨没有Ixed a约束。有什么方法,漂亮或丑陋,来实现这个?我无法添加Ixed a到实例上下文,因为Update构造函数需要为不满足此约束的值工作。

{-# LANGUAGE GADTs #-}

import Control.Lens (Index, Ixed)
import Data.Serialize

-- | Two different ways of updating a value - replacing it completely or,
-- if it is an instance of Ixed, re-ordering it.
data Op a where
  Update :: Serialize a => a -> Op a
  Reorder :: (Ixed a, Serialize (Index a)) => [Index a] -> Op a

instance Serialize a => Serialize (Op a) where
  put (Update a) = putWord8 1 >> put a
  put (Reorder ks) = putWord8 2 >> put ks
  get = do
    i <- getWord8
    case i of
      1 -> Update <$> get
      2 -> Reorder <$> get
      _ -> error "instance Serialize (Op a) - corrupt data"

附录:对此的一种简化可能是使类型变量a成为幻像类型,因此Op看起来像这样:

data Op a where
  Update :: Serialize a => ByteString -> Op a
  Reorder :: (Ixed a, Serialize (Index a)) => [ByteString] -> Op a

然后可以使用该类型正确解码字节字符串。不确定这是否有帮助

标签: haskellghcgadt

解决方案


通常,您要尝试做的事情是不可能的。本质上,您试图让 GHC 推断Ixed aSerialize (Index a)仅给出Serialize a. 当然,这可能适用于您想到的任何用例,但它通常不起作用,这就是 GHC 拒绝您的实例的原因。

我说“一般来说不可能”,因为如果你指定你关心的类型,那么它绝对是可能的。这意味着您必须列出所有可以序列化的类型Reorder,但这确实与您将获得的一样好。

有多种方法可以做到这一点,但我认为最简单的方法是使用constraints包在Dict. 您首先要定义:

class MaybeSerializeIndexed a where
  canSerializeIndex :: Maybe (Dict (Ixed a, Serialize (Index a)))
  default canSerializeIndex :: (Ixed a, Serialize (Index a)) -> Maybe (Dict (Ixed a, Serialize (Index a)))
  canSerializeIndex = Just Dict

默认签名(需要DefaultSignaturespragma)是让您的生活变得简单的关键,因为这意味着您可以使用简单的一行来列出您关心的类型,如下所示:

instance Serialize a => MaybeSerializeIndexed [a]
instance Serialize k => MaybeSerializeIndexed (Map k a)

除此之外,您可以创建一个重叠实例来处理适用的类型Reorder

instance {-# OVERLAPPABLE #-} MaybeSerializeIndexed a where
  canSerializeIndex = Nothing

有了这个机器,您可以编写您的实例:

instance (MaybeSerializeIndexed a, Serialize a) => Serialize (Op a) where
  put (Update a) = putWord8 1 >> put a
  put (Reorder ks) = putWord8 2 >> put ks
  get = do
    i <- getWord8
    case (i, canSerializeIndex @a) of
      (1, _)         -> Update <$> get
      (2, Just Dict) -> Reorder <$> get
      _ -> error "instance Serialize (Op a) - corrupt data"

请注意,将MaybeSerializeIndexed a约束添加到您的实例实际上并不是什么大问题,因为每种类型都有一个实例。这也意味着,如果您在系统中添加新类型而不为其添加MaybeSerializeIndexed实例,那么当您尝试反序列化它时将不会收到类型错误 - 您只会收到运行时错误。例如,如果你Foo在你知道的地方添加了一个新类型Ixed FooSerialize (Index Foo)但你没有添加instance MaybeSerializeIndexed Foo,那么如果你编写一个尝试获取值的程序,你将不会收到类型错误getFoo但在运行时会收到运行时错误它。


推荐阅读