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
它们之间是否有任何区别:代码与一个编译而不是另一个编译,或者给出不同的结果?
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
接收多态类型。
Existentially quantified data constructors喜欢
data Foo = forall a. MkFoo a (a -> Bool)
| Nil
可以轻松转换为 GADT:
data Foo where
MkFoo :: a -> (a -> Bool) -> Foo
Nil :: Foo
它们之间是否有任何区别:代码与一个编译而不是另一个编译,或者给出不同的结果?
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
接收多态类型。