变体与 GADT 方法

Posted

技术标签:

【中文标题】变体与 GADT 方法【英文标题】:Variant vs GADT approach 【发布时间】:2018-02-20 11:22:11 【问题描述】:

在 OCaml 中,我想定义一个函数 f,它接受 input 来更新记录 x。在以下两种方法中,我感兴趣的是其中一种是否比另一种更具优势(撇开可读性不谈)。

变体方法

type input =
  | A of int
  | B of string

let f x = function
  | A a ->  x with a 
  | B b ->  x with b 

GADT 方法

type _ input =
  | A : int input
  | B : string input

let f (type t) x (i: t input) (v: t) =
  match i with
  | A ->  x with a = v 
  | B ->  x with b = v 

【问题讨论】:

变体方法在实现和使用方面具有简单、直接和易于理解的巨大优势。然而,GADT 方法可以部分应用于 input 类型,而无需提供值,但这只是您真正需要的优势。此类问题的答案几乎总是在很大程度上取决于上下文,因为几乎总是需要权衡取舍。如果没有,就不需要这个不太有利的功能。 谢谢,我以为是这样。可能应该将我的问题表述为“各有利弊”。除了解析数据结构之外,识别 GADT 的用例有点麻烦。 【参考方案1】:

ADT 专家:

简单明了,不需要类型注释或任何花哨的东西 编写string -> input 类型的函数很简单。

GADT 专业人士:

避免一层拳击。 但是,如果您需要解析函数,这将完全否定,这将迫使您将东西打包在存在主义之下。

更准确地说,GADT 版本可以看作是 ADT 版本的分解。你可以系统地把一个转换成另一个,内存布局会差不多(借助一个小注解):

type a and b and c

type sum =
  | A of a
  | B of b
  | C of c

type _ tag =
  | A : a tag
  | B : b tag
  | C : c tag

type deppair = Pair : ('a tag * 'a) -> deppair [@@ocaml.unboxed]

let pack (type x) (tag : x tag) (x : x) = Pair (tag, x)
let to_sum (Pair (tag, v)) : sum = match tag with
  | A -> A v
  | B -> B v
  | C -> C v

let of_sum : sum -> deppair = function
  | A x -> pack A x
  | B x -> pack B x
  | C x -> pack C x

【讨论】:

在内存表示中装箱? 那个符号对我来说是新的 :) [@@ocaml.unboxed] 是一个类型约束,表明 deppair 不能与存在值一起使用吗? 一点也不,它是一个注解,向编译器指示应该优化内存表示。见caml.inria.fr/pub/docs/manual-ocaml/extn.html#sec260 为了澄清这里使用的术语,是type deppair 包装了主要的gadt 类型,这里是存在的吗?这也是使用 GADT 时的一般模式,即总是必须在“存在”中包装/解开一个 gadt 以模拟正常的 ADT 功能? @Drup GADT 表示为带有包含作为字段的参数的标记的块。就像 ADT 一样。那么你说哪里少了一层拳击呢?实际上,在您的代码中,ADT 是一个带有标签和一个字段的块,而 Pair 是一个带有标签和两对的块。这不是在浪费内存吗?【参考方案2】:

正如您所注意到的,GADT 的(非)可读性是一个很大的缺点。尽可能避免使用 GADT。更容易打字,更容易阅读。也不太复杂的错误消息。

在运行时简化它们是相同的。它们表示为带有标签和字段的简单整数或块,代码使用标签进行匹配和分支。所以两者都不会给你带来优势。

在编译时 GADT 更强大,因为编译器可以以 ADT 不允许的方式检查类型。一个示例是存在类型,例如另一个答案中的示例。因此,当您无法使用 ADT 时,请使用 GADT。

【讨论】:

以上是关于变体与 GADT 方法的主要内容,如果未能解决你的问题,请参考以下文章

使用 Hedgehog(或任何其他基于属性的测试框架)生成随机 GADT 的最安全方法

Scala 中单例类型的 GADT 类型细化

我无法让基于 GADT 的玩具动态类型与参数类型一起使用

Java中的参数化类型(GADT)

函数返回GADT的任何构造函数的结果

如何在 GADT 中表示此 FSM