关于您的问题 2(如何在 Haskell 98 中编码 GADT 用例),您可能需要查看 Sulzmann 和 Wang 在 2006 年发表的这篇论文:Haskell 98 中的 GADTless 编程。
就像您所指的 OCaml 工作一样,它通过相等类型分解 GADT 来工作。有多种定义相等类型的方法;他们使用一种类似于 OCaml 的 Leibniz 等式形式,它允许通过 kind 类型运算符的任何应用程序进行替换* -> *
。
根据给定类型检查器对 GADT 相等性的推理方式,这可能不足以表达以涵盖 GADT 的所有示例:检查器可能会实现此定义不一定捕获的相等性推理规则。例如,如果只在a*b = c*d
kind应用类型构造函数a = c
,b = d
就不会出现这种形式的分解* -> *
。2010 年晚些时候,Oleg 讨论了如何使用类型族通过 Leibniz 等式应用“类型解构器”,从而获得该定义的分解属性——当然这又在 Haskell 98 之外。
对于类型系统设计者来说,这是要牢记的:你的语言对于莱布尼茨等式是否完整,从某种意义上说,它可以表达专门的等式求解器可以做什么?
即使你找到了一个足够表达的相等类型的编码,你也会遇到非常实用的方便问题:当你使用 GADT 时,所有相等见证的使用都是从类型注释中推断出来的。使用这种显式编码,您将有更多工作要做。
最后(没有双关语),GADT 的许多用例都可以通过无标记最终嵌入(同样由 Oleg 再次表示)来表达,IIRC 通常可以在 Haskell 98 中完成。Martin Van Steenbergen的博客文章指出在其回复的评论中是本着这种精神,但奥列格已经大大改进了这种技术。