haskell - 如何为此 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
然后可以使用该类型正确解码字节字符串。不确定这是否有帮助
解决方案
通常,您要尝试做的事情是不可能的。本质上,您试图让 GHC 推断Ixed a
并Serialize (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
默认签名(需要DefaultSignatures
pragma)是让您的生活变得简单的关键,因为这意味着您可以使用简单的一行来列出您关心的类型,如下所示:
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 Foo
,Serialize (Index Foo)
但你没有添加instance MaybeSerializeIndexed Foo
,那么如果你编写一个尝试获取值的程序,你将不会收到类型错误get
,Foo
但在运行时会收到运行时错误它。
推荐阅读
- sql - 依靠“假”列获取正确记录
- c# - MetadataExtractor 为什么我不能在控制台外打印我的信息
- sas - 将 SAS 数据集转换为带有文本属性双引号的管道分隔文本文件
- javascript - 如何在纯 JavaScript 中只绑定一次事件监听器?
- c++11 - 最佳实践:在不同的类中初始化不同的静态私有参数
- http - 如何使用 Web 浏览器连接到 Tcp 服务器?
- java - VSCode Maven错误`指定的编译器合规性是1.7,但使用了JRE 13`
- php - 比较两个多维对象及其内部的数组
- javascript - SailsJS 模型中的嵌套对象
- python - 使用 Pygame 显示内容的问题