Haskell:获取一个Get monad,并返回无限类型上的多态性



如果以下内容是基于我的误解,我提前道歉。我在任何地方都找不到我需要的答案。

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

现在,我想到的这个想法(听起来非常优雅)如下:如果方法(解析类型描述符)创建了一个Get-Monad,但不运行它,然后可以用保存数据的ByteString运行,该怎么办?

这就需要这样一种方法:

parseTypeDescriptor :: Get (Get a)

其中,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 --

"action"的类型应为"Get a"。在TODO中,我需要构造一个执行n次操作的值,将这些值放入一个数组中,并在该数组周围放置一个Get。

示例:如果action=getWord16be并且n=2;那么TODO应该等价于:

TODO :: Get [a]
TODO = do x <- getWord16be
          y <- getWord16be
          return [x,y]

我有3个问题:

  1. 我不知道TODO上的什么代码能满足我的要求
  2. 编译器告诉根方法(parseTypeDescriptor)不能返回多个类型。如果它返回Get(Get Word8),它就不能还返回Get(Get[Word8])
  3. 我不确定运行顶级Get时是否可以避免它创建的Gets也在运行

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

你并没有真正返回多态的东西(通用量化)——事实上,你总是返回一个具体的类型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

到目前为止还不错。。。问题是,使用GetSomething实际上没有任何有用的方法,因为无法知道它实际产生的类型(它隐藏在GADT中)。有一些方法可以克服这一点——例如,如果所有解析的类型都属于一个公共类型类,则可以将其添加为约束:

data GetSomething where
   GetSomething :: CommonClass a => Get a -> GetSomething

这样,即使不知道确切的类型,您也可以解析值并对其进行处理。

然而,这可能不是最好的方法:基本上,你必须在丢失类型信息后运行,然后四处破解才能完成任务。在Haskell中,存在主义被认为是一种反模式。

更好的解决方案可能是将类型放在一个清晰的替代数据结构中:

data GetSomething
   = GetW8 (Get Word8)
   | GetW16 (Get Word16)
   | ...
   | GetList [GetSomething]

然后,你可以简单地进行模式匹配,找出你拥有的特定类型,因为支持的类型列表是有限的,你可以确保你不会因为一个类型实际上不受支持而遇到问题。

最新更新