Haskell 如何为此GADT编写序列化实例?

Haskell 如何为此GADT编写序列化实例?,haskell,ghc,gadt,Haskell,Ghc,Gadt,下面是一个模块,它试图为一个简单的GADT实现一个序列化实例。不幸的是,Reorder构造函数的get实现抱怨没有Ixed约束。有没有办法,美的还是丑的,来实现这一点?我无法将Ixed a添加到实例上下文中,因为Update构造函数需要处理不满足此约束的值 {-# LANGUAGE GADTs #-} import Control.Lens (Index, Ixed) import Data.Serialize -- | Two different ways of updating a va

下面是一个模块,它试图为一个简单的GADT实现一个
序列化
实例。不幸的是,
Reorder
构造函数的
get
实现抱怨没有
Ixed约束。有没有办法,美的还是丑的,来实现这一点?我无法将
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"

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

根据@AntC的评论,可能需要重新思考为什么需要
Op
作为GADT。然而,这里有一种方法似乎是有效的

Haskell的基本原则是,您可以要求一个实例
Ixed a
,但不能根据实例
Ixed a
是否存在而有条件地采取行动。因此,无论如何,您必须显式地枚举要在此序列化中使用的所有类型
a
,并手动指示哪些类型将和哪些类型将被视为
Ixed

一旦你听天由命,有一个显而易见的解决办法。如果要为
a~Int
(非
Ixed
)和
a~[Int]
(使用
Ixed
)支持
Op a
,可以定义两个实例:

instance Serialize (Op Int) 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
      _ -> error "instance Serialize (Op a) - corrupt data"

instance Serialize (Op [Int]) 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
功能的
hasixed
类型选择:

instance (Serialize a) => OpVal' False a where
  getOp = do
    i <- getWord8
    case i of
      1 -> Update <$> get
      _ -> error "instance Serialize (Op a) - corrupt data"
instance (Ixed a, Serialize (Index a), Serialize a) => OpVal' True a where
  getOp = do
    i <- getWord8
    case i of
      1 -> Update <$> get
      2 -> Reorder <$> get
      _ -> error "instance Serialize (Op a) - corrupt data"
它指定类型
a
是否包含
Ixed a
。然后,我们可以使用另一个类型族根据
HasIxed
选择正确的
OpVal'
实例:

type family OpVal a where
  OpVal a = OpVal' (HasIxed a) a
最后,我们可以定义我们的
序列化(Op a)
实例:

instance OpVal a => Serialize (Op a) where
  put (Update a) = putWord8 1 >> put a
  put (Reorder ks) = putWord8 2 >> put ks
  get = getOp @(HasIxed a)
有了它,您可以将类型
a
添加到打开的
HasIxed
类型族中:

type family HasIxed a :: Bool
type instance HasIxed Int = False
type instance HasIxed [Int] = True
这一切都只是一种工作:

instance OpVal a => Serialize (Op a) where
  put (Update a) = putWord8 1 >> put a
  put (Reorder ks) = putWord8 2 >> put ks
  get = getOp @(HasIxed a)

data BigThing a b = BigThing (Op a) (Op b) deriving (Generic)
instance (OpVal a, OpVal b) => Serialize (BigThing a b)

main = do
  let s = runPut $ put (BigThing (Update (5 :: Int)) (Reorder @[Int] [1,2,3]))
      Right (BigThing (Update x) (Reorder xs)) = runGet (get :: Get (BigThing Int [Int])) s
  print (x, xs)
完整示例:

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

import GHC.Generics (Generic)
import Control.Lens (Index, Ixed)
import Data.Serialize

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

class OpVal' (hasixed :: Bool) a where
  getOp :: Get (Op a)
instance (Serialize a) => OpVal' False a where
  getOp = do
    i <- getWord8
    case i of
      1 -> Update <$> get
      _ -> error "instance Serialize (Op a) - corrupt data"
instance (Ixed a, Serialize (Index a), Serialize a) => OpVal' True a where
  getOp = do
    i <- getWord8
    case i of
      1 -> Update <$> get
      2 -> Reorder <$> get
      _ -> error "instance Serialize (Op a) - corrupt data"

type family HasIxed a :: Bool
type instance HasIxed Int = False
type instance HasIxed [Int] = True

type family OpVal a where
  OpVal a = OpVal' (HasIxed a) a

instance OpVal a => Serialize (Op a) where
  put (Update a) = putWord8 1 >> put a
  put (Reorder ks) = putWord8 2 >> put ks
  get = getOp @(HasIxed a)

data BigThing a b = BigThing (Op a) (Op b) deriving (Generic)
instance (OpVal a, OpVal b) => Serialize (BigThing a b)

main = do
  let s = runPut $ put (BigThing (Update (5 :: Int)) (Reorder @[Int] [1,2,3]))
      Right (BigThing (Update x) (Reorder xs)) = runGet (get :: Get (BigThing Int [Int])) s
  print (x, xs)
{-#语言允许使用模糊类型}
{-#语言数据类型}
{-#派生通用语言}
{-#语言灵活语境#-}
{-#语言灵活实例}
{-#语言GADTs}
{-#语言MultiParamTypeClasses}
{-#语言范围类型变量#-}
{-#语言类型应用程序{-}
{-#语言类型族{-}
{-#语言不可判定实例}
进口GHC.仿制药(仿制药)
进口控制。镜头(索引,Ixed)
导入数据。序列化
数据运算a在哪里
更新::序列化a=>a->Op a
重新排序::(Ixed a,序列化(索引a))=>[索引a]->操作a
类OpVal'(hasixed::Bool)a其中
getOp::Get(opa)
实例(序列化a)=>OpVal'False a其中
getOp=do
我得到最新消息
_->错误“实例序列化(Op a)-数据损坏”
实例(Ixed a,Serialize(索引a),Serialize a)=>OpVal'True a其中
getOp=do
我得到最新消息
2->重新排序获取
_->错误“实例序列化(Op a)-数据损坏”
类型族HasIxed a::Bool
类型实例HasIxed Int=False
类型实例HasIxed[Int]=True
键入family OpVal a,其中
OpVal a=OpVal'(HasIxed a)a
实例OpVal a=>Serialize(opa),其中
放置(更新a)=放置文字8 1>>放置
put(重新排序ks)=putWord8 2>>put ks
get=getOp@(HasIxed a)
数据BigThing a b=BigThing(Op a)(Op b)派生(通用)
实例(OpVal a,OpVal b)=>Serialize(BigThing a b)
main=do
设s=runPut$put(BigThing(Update(5::Int))(Reorder@[Int][1,2,3]))
右(BigThing(updatex)(reorderxs))=runGet(get::get(bigthint Int[Int]))s
打印(x,xs)

一般来说,你想做的事情是不可能的。本质上,您试图让GHC推断出
Ixed a
序列化(索引a)
仅给定
序列化a
。当然,这可能适用于您想到的任何用例,但通常不起作用,这就是GHC拒绝您的实例的原因

我说“一般来说不可能”,因为如果你指定你关心的类型,那么它肯定是可能的。这意味着您必须列出所有可以从
Reorder
序列化的类型,但这是您将要得到的最好结果

有多种方法可以做到这一点,但我认为最简单的方法是使用
约束
包在
Dict
中捕获所需内容。您可以从定义以下内容开始:

类可能被序列化为
canSerializeIndex::Maybe(Dict(Ixed a,Serialize(Index a)))
默认canSerializeIndex::(Ixed a,Serialize(Index a))->可能(Dict(Ixed a,Serialize(Index a)))
CanSerializedIndex=仅记录
默认签名(需要
DefaultSignatures
pragma)是使您的生活变得简单的关键,因为它意味着您可以用简单的一行程序列出您关心的类型,如:

实例序列化a=>MaybeSerializeIndexed[a]
实例序列化k=>MaybeSerializeIndexed(映射k a)
除此之外,您还可以创建一个重叠实例来处理不适用于
重新排序的类型:

实例{-#可重叠#-}可以序列化为
索引=无
使用此机制,您可以编写实例:

instance OpVal a => Serialize (Op a) where
  put (Update a) = putWord8 1 >> put a
  put (Reorder ks) = putWord8 2 >> put ks
  get = getOp @(HasIxed a)
instance(可能是序列化a,序列化a)=>Serialize(Op a),其中
放置(更新a)=放置文字8 1>>放置
put(重新排序ks)=putWord8 2>>put ks
得到=做
我得到最新消息
(2,直接听写)->重新排序获取
_->错误“实例序列化(Op a)-数据损坏”
请注意,向实例添加
maybeserializedA
约束并不是什么大问题,因为每种类型都有一个实例。这也意味着,如果您向系统中添加新类型,而没有为其添加
MaybeSerializeIndexed
实例,那么在尝试对其进行反序列化时不会出现类型错误,只会出现运行时错误。例如,如果y
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

import GHC.Generics (Generic)
import Control.Lens (Index, Ixed)
import Data.Serialize

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

class OpVal' (hasixed :: Bool) a where
  getOp :: Get (Op a)
instance (Serialize a) => OpVal' False a where
  getOp = do
    i <- getWord8
    case i of
      1 -> Update <$> get
      _ -> error "instance Serialize (Op a) - corrupt data"
instance (Ixed a, Serialize (Index a), Serialize a) => OpVal' True a where
  getOp = do
    i <- getWord8
    case i of
      1 -> Update <$> get
      2 -> Reorder <$> get
      _ -> error "instance Serialize (Op a) - corrupt data"

type family HasIxed a :: Bool
type instance HasIxed Int = False
type instance HasIxed [Int] = True

type family OpVal a where
  OpVal a = OpVal' (HasIxed a) a

instance OpVal a => Serialize (Op a) where
  put (Update a) = putWord8 1 >> put a
  put (Reorder ks) = putWord8 2 >> put ks
  get = getOp @(HasIxed a)

data BigThing a b = BigThing (Op a) (Op b) deriving (Generic)
instance (OpVal a, OpVal b) => Serialize (BigThing a b)

main = do
  let s = runPut $ put (BigThing (Update (5 :: Int)) (Reorder @[Int] [1,2,3]))
      Right (BigThing (Update x) (Reorder xs)) = runGet (get :: Get (BigThing Int [Int])) s
  print (x, xs)