Haskell:获取一个获取monad并且还返回无限类型的多态性

sil*_*ver 4 serialization haskell

如果以下是基于我的误解,我会提前道歉.我无法找到我需要的答案.

我正在实现一个程序,从文件中读取序列化对象,使用惰性评估.我正在解析该文件,首先将其作为单个Lazy ByteString读取,然后使用Get Monad解析它.在文件中,存在一个存储类型描述符的点,以及存储数据的另一个点.类型描述符告诉我如何解释数据以及它最终具有哪种类型,并且该类型可以采用嵌套形式; Double或[[Word8]]是两种可能性.

现在,我想出的这个想法(以及简单的听起来非常优雅)的想法如下:如果方法(解析类型描述符)创建 - 但不运行 - 如果稍后可以运行Get Monad 使用保存数据的ByteString?

这将需要一个像这样的方法:

parseTypeDescriptor :: Get (Get a)
Run Code Online (Sandbox Code Playgroud)

其中a与描述符描述的类型相同(意味着它可以采用嵌套形式).以下是代码的一些部分:

parseTypeDescriptor :: Get (Get a)
-- first part of the type descriptor is an id (Word8) that implies a type
parseTypeDescriptor = getWord8 >>= go
   where go 0 = return getWord8
         go 1 = return getWord16be
         go 2 = return getWord32be
                 ...
         -- id 5 indicates that the type is an array
         -- this means two more values are coming;
         -- the first indicates the array's length, the second its type
         go 5 = do n      <- getWord8
                   action <- parseTypeDescriptor
                   return $ -- TODO --
Run Code Online (Sandbox Code Playgroud)

"行动"的类型应为"获取".我需要的--TODO - 是构造一个执行n次动作的值,将这些值放在一个数组中,然后放置一个Get数组.

示例:if action = getWord16be且n = 2; 然后TODO应该等同于:

TODO :: Get [a]
TODO = do x <- getWord16be
          y <- getWord16be
          return [x,y]
Run Code Online (Sandbox Code Playgroud)

我有3个问题:

  1. 我不知道什么代码--TODO - 做我想要的
  2. 编译器告诉我root方法(parseTypeDescriptors)不能返回多个类型.如果它返回Get(获取Word8),它也不能返回Get(Get [Word8])
  3. 我不确定当运行顶级Get时,是否可以避免运行它所创建的获取

我开始怀疑这实际上是不可能的,但我希望我错了 - 我的解释是可以理解的.

lef*_*out 5

你并没有真正返回多态(普遍量化)的东西 - 事实上你总是返回一个具体的类型Get A,但它只会在运行时决定它是什么.这称为存在量化.Haskell98没有任何这样的东西,但你可以使用广义代数数据类型:

{-# LANGUAGE GADTs #-}
data GetSomething where
   GetSomething :: Get a -> GetSomething

parseTypeDescriptor :: Get GetSomething
-- first part of the type descriptor is an id (Word8) that implies a type
parseTypeDescriptor = getWord8 >>= go
   where go 0 = return $ GetSomething getWord8
         go 1 = return $ GetSomething getWord16be
         go 2 = return $ GetSomething getWord32be
Run Code Online (Sandbox Code Playgroud)

到目前为止一切都那么好......麻烦的是,你可以做的事情没有什么用处GetSomething,因为没有办法知道它实际产生的是什么类型(它隐藏在GADT中).有一些方法可以解决这个问题 - 例如,如果所有已解析的类型都属于公共类型类,则可以将其添加为约束:

data GetSomething where
   GetSomething :: CommonClass a => Get a -> GetSomething
Run Code Online (Sandbox Code Playgroud)

这样,即使不知道确切的类型,您也可以解析值并对它们执行某些操作.

然而,这可能不是最好的方法:你基本上总是必须在丢失的类型信息之后运行并且仍然可以完成任务.存在被认为是Haskell中的反模式.

更好的解决方案是将类型放在一个明确的替代数据结构中:

data GetSomething
   = GetW8 (Get Word8)
   | GetW16 (Get Word16)
   | ...
   | GetList [GetSomething]
Run Code Online (Sandbox Code Playgroud)

然后你可以简单地模式匹配以找出你拥有的特定类型,并且因为支持的类型列表是有限的,你可以确定你不会遇到问题,因为实际上不支持类型.