解析和使用GADT

时间:2015-10-29 08:12:24

标签: haskell gadt attoparsec

我在编写解析器时遇到了问题。具体来说,我想成为不同类型的返回值。例如,我有两种不同的数据类型FAPA来表示两种不同的脂类 -

data FA = ClassLevelFA IntegerMass
        | FA           CarbonChain
        deriving (Show, Eq, Ord)

data PA   = ClassLevelPA       IntegerMass
          | CombinedRadylsPA   TwoCombinedRadyls
          | UnknownSnPA        Radyl Radyl
          | KnownSnPA          Radyl Radyl
          deriving (Show, Eq, Ord)

使用attoparsec,我已经构建了解析器来解析脂质速记符号。对于上面的数据类型,我有解析器faParserpaParser。我希望能够解析FAPA。但是,由于FAPA是不同的数据类型,我无法执行以下操作 -

inputParser =  faParser
           <|> paParser

我最近了解了GADT,我认为这可以解决我的问题。因此,我去做了一个GADT和一个eval函数,并更改了解析器faParserpaParser。 -

data ParsedLipid a where
  ParsedFA :: FA -> ParsedLipid FA
  ParsedPA :: PA -> ParsedLipid PA

eval :: ParsedLipid a -> a
eval (ParsedFA val) = val
eval (ParsedPA val) = val

这让我接近但看起来好像ParsedFAParsedPA是不同的数据类型?例如,解析"PA 17:1_18:1"会给我一个类型为ParsedLipid PA的值(不仅仅是ParsedLipid,正如我所料)。因此,解析器inputParser仍然不进行类型检查。

let lipid = use "PA 17:1_18:1"
*Main> :t lipid
lipid :: ParsedLipid PA

关于如何解决这个问题的任何建议?

2 个答案:

答案 0 :(得分:5)

@MathematicalOrchid指出你可能不需要GADT,而简单的和类型就足够了。您可能有一个XY problem,但我对您的用例了解不够,无法做出明确的判断。这个答案是关于如何将无类型数据转换为GADT。

正如您所注意到的,您的解析功能无法返回ParsedLipid a,因为这使得调用上下文可以自由选择a,这是没有意义的; a实际上是由输入数据决定的。并且您无法返回ParsedLipid FAParsedLipid PA,因为输入数据可能属于任何一种类型。

因此,从运行时数据构建GADT时的标准技巧 - 当您事先不知道索引的类型时 - 是使用existential quantification

data AParsedLipid = forall a. AParsedLipid (ParsedLipid a)

类型参数a显示在AParsedLipid的右侧,但不在左侧。值AParsedLipid保证包含格式良好的ParsedLipid,但其精确类型保密。存在主义类型是一个包装器,它可以帮助我们从凌乱的,无类型的现实世界转变为干净,强烈类型的GADT。

将存在量化的包装器推到系统的边缘是一个好主意,在那里你必须与外部世界进行通信。您可以在核心模型中编写带有ParsedLipid a -> a等签名的函数,并将它们应用于边缘存在的包装器下的数据。您验证输入,将其包装为存在类型,然后使用强类型模型安全地处理它 - 因为您已经检查了输入,所以不必担心错误。

您可以解压缩AParsedLipid以获取ParsedLipid,并在其上进行模式匹配,以便在运行时确定a是什么 - 它可能是{{1 }或FA

PA

您注意到,由于上述原因,showFA :: FA -> String showFA = ... showPA :: PA -> String showPA = ... showLipid :: AParsedLipid -> String showLipid (AParsedLipid (ParsedFA x)) = "AParsedLipid (ParsedFA "++ showFA x ++")" showLipid (AParsedLipid (ParsedPA x)) = "AParsedLipid (ParsedPA "++ showPA x ++")" 无法出现在a的函数的返回类型中。返回类型必须为编译器所知;此技术不允许您使用不同的返回类型定义&#34;函数&#34;。

当您构建AParsedLipid时,您必须生成足够的证据来说服编译器包裹的AParsedLipid格式正确。在您的示例中,这归结为解析良好类型的ParsedLipidPA然后将其包装起来。

FA

与运行时数据一起使用时,GADT有点尴尬。存在性包装器有效地擦除parser :: Parser AParsedLipid parser = AParsedLipid <$> (fmap ParsedFA faParser <|> fmap ParsedPA paParser) 中的额外编译时信息 - ParsedLipidAParsedLipid同构。 (证明代码中的同构是一个很好的练习。)根据我的经验,GADT在构建程序方面比在构建数据方面要好得多 - 他们擅长实现强类型嵌入式域特定语言的类型&#39;索引可以在编译时知道。例如,Yampaextensible-effects都使用GADT作为其中心数据类型。这有助于编译器检查您在编写的代码中是否正确使用了特定于域的语言(在某些情况下允许某些优化)。您不太可能在运行时从真实数据中构建FRP网络或monadic效果。

答案 1 :(得分:3)

你到底想要什么?

如果您在编译时知道 是否需要FAPA,那么GADT是一种很好的方法。

如果您想在运行时决定 来解析FAPA,您可以使用... Either FA PA