GADT 的这种使用是否完全等同于存在类型?

Is this use of GADTs fully equivalent to existential types?

Existentially quantified data constructors喜欢

data Foo = forall a. MkFoo a (a -> Bool)
         | Nil

可以轻松转换为 GADT:

data Foo where 
    MkFoo :: a -> (a -> Bool) -> Foo
    Nil :: Foo

它们之间是否有任何区别:代码与一个编译而不是另一个编译,或者给出不同的结果?

来自the documentation

Notice that GADT-style syntax generalises existential types (Existentially quantified data constructors). For example, these two declarations are equivalent:

data Foo = forall a. MkFoo a (a -> Bool)
data Foo' where { MKFoo :: a -> (a->Bool) -> Foo' }

(强调等效词)

后者实际上不是 GADT - 它是用 GADT 语法声明的存在量化数据类型。因此,它与前者相同。

它不是 GADT 的原因是没有类型变量可以根据构造函数的选择进行细化。这是 GADT 添加的关键新功能。如果你有这样的 GADT:

data Foo a where
    StringFoo :: String -> Foo String
    IntFoo :: Int -> Foo Int

然后 pattern-matching 在每个构造函数上显示可以在匹配子句中使用的附加信息。例如:

deconstructFoo :: Foo a -> a
deconstructFoo (StringFoo s) = "Hello! " ++ s ++ " is a String!"
deconstructFoo (IntFoo i)    = i * 3 + 1

请注意,从类型系统的角度来看,这里发生了一些非常有趣的事情。 deconstructFoo 承诺它将适用于 任何 选择 a,只要它传递了 Foo a 类型的值。但是第一个方程returns一个String,第二个方程returns一个Int.

这是常规数据类型无法做到的,也是 GADT 提供的新功能。在第一个等式中,模式匹配将约束 (a ~ String) 添加到其上下文中。在第二个等式中,模式匹配添加 (a ~ Int).

如果您还没有创建 pattern-matching 可以导致类型细化的类型,那么您就没有 GADT。您只需要一个使用 GADT 语法声明的类型。这很好——在很多方面,它是比基本数据类型语法更好的语法。对于最简单的情况,它更冗长。

它们几乎相同,尽管不完全相同,具体取决于您打开的扩展程序。

首先,请注意,您无需启用 GADTs 扩展即可对存在类型使用 data .. where 语法。启用以下较小的扩展就足够了。

{-# LANGUAGE GADTSyntax #-}
{-# LANGUAGE ExistentialQuantification #-}

有了这些扩展,你就可以编译

data U where
    U :: a -> (a -> String) -> U

foo :: U -> String
foo (U x f) = f x

g x = let h y = const y x
      in  (h True, h 'a')

如果我们用

替换扩展名和类型定义,上面的代码也可以编译
{-# LANGUAGE ExistentialQuantification #-}
data U = forall a . U a (a -> String)

但是,上面的代码 不会 GADTs 扩展打开的情况下进行编译!这是因为 GADTs 也开启了 MonoLocalBinds 扩展,这阻止了上面定义的 g 编译。这是因为后一个扩展阻止 h 接收多态类型。