GADT通过提供隐式的forall,为使用现有类型的代码提供清晰,更好的语法
我认为人们普遍认为GADT语法更好。我并不是说这是因为GADT提供了隐式的forall,而是因为带有ExistentialQuantification
扩展名的原始语法可能会造成混淆/误导。该语法当然看起来像:
data SomeType = forall a. SomeType a
或有约束:
data SomeShowableType = forall a. Show a => SomeShowableType a
我认为共识是,forall
此处使用关键字可以使类型容易与完全不同的类型混淆:
data AnyType = AnyType (forall a. a) -- need RankNTypes extension
更好的语法可能使用了单独的exists
关键字,因此您应该编写:
data SomeType = SomeType (exists a. a) -- not valid GHC syntax
GADT语法(无论与隐式还是显式一起使用)forall
在这些类型之间都更加统一,并且似乎更易于理解。即使具有显式forall
的定义,以下定义也可以使您理解任何类型的值都可以a
放入单态中SomeType'
:
data SomeType' where
SomeType' :: forall a. (a -> SomeType') -- parentheses optional
而且很容易看到和理解该类型与:
data AnyType' where
AnyType' :: (forall a. a) -> AnyType'
现有类型似乎对它们包含的类型不感兴趣,但是与它们匹配的模式表示存在某种类型,除非&使用Typeable或Data,否则在&之前我们不知道它是什么类型。
当我们要隐藏类型时(例如,对于异构列表),或者在编译时我们真的不知道什么类型时,我们会使用它们。
我猜这些距离还不太远,尽管您不必使用Typeable
或Data
使用存在性类型。我认为说存在类型在未指定类型的周围提供了类型良好的“盒子”会更准确。该框在某种意义上确实“隐藏”了该类型,这使您可以创建此类框的异构列表,而忽略它们包含的类型。事实证明,像SomeType'
上面这样的无约束的存在是非常无用的,但是是受约束的类型:
data SomeShowableType' where
SomeShowableType' :: forall a. (Show a) => a -> SomeShowableType'
允许您进行模式匹配以在“框”内窥视,并使类型类工具可用:
showIt :: SomeShowableType' -> String
showIt (SomeShowableType' x) = show x
请注意,这适用于任何类型类,而不仅仅是Typeable
or Data
。
关于您对幻灯片第20页的困惑,作者说,对于一个存在性 Worker
需求的函数来说,要求Worker
具有特定Buffer
实例是不可能的。您可以编写一个函数来Worker
使用的特定类型创建Buffer
,例如MemoryBuffer
:
class Buffer b where
output :: String -> b -> IO ()
data Worker x = forall b. Buffer b => Worker {buffer :: b, input :: x}
data MemoryBuffer = MemoryBuffer
instance Buffer MemoryBuffer
memoryWorker = Worker MemoryBuffer (1 :: Int)
memoryWorker :: Worker Int
但是,如果您编写一个带有Worker
as参数的函数,则该函数只能使用常规Buffer
类型类工具(例如function output
):
doWork :: Worker Int -> IO ()
doWork (Worker b x) = output (show x) b
b
即使通过模式匹配,它也不能试图要求它是特定类型的缓冲区:
doWorkBroken :: Worker Int -> IO ()
doWorkBroken (Worker b x) = case b of
MemoryBuffer -> error "try this" -- type error
_ -> error "try that"
最后,可以通过所涉及类型类的隐式“字典”参数来获得有关存在性类型的运行时信息。Worker
上面的类型除了具有用于缓冲区和输入的字段外,还具有一个不可见的隐式字段,该字段指向Buffer
字典(类似于v-table,尽管它几乎不大,因为它仅包含指向适当output
函数的指针)。
在内部,类型类Buffer
表示为具有函数字段的数据类型,实例是该类型的“字典”:
data Buffer' b = Buffer' { output' :: String -> b -> IO () }
dBuffer_MemoryBuffer :: Buffer' MemoryBuffer
dBuffer_MemoryBuffer = Buffer' { output' = undefined }
存在类型对此字典有一个隐藏字段:
data Worker' x = forall b. Worker' { dBuffer :: Buffer' b, buffer' :: b, input' :: x }
像doWork
这样对存在Worker'
值进行操作的函数实现为:
doWork' :: Worker' Int -> IO ()
doWork' (Worker' dBuf b x) = output' dBuf (show x) b
对于仅具有一个功能的类型类,该字典实际上已优化为一个新Worker
类型,因此在此示例中,存在类型包括一个隐藏字段,该字段由指向output
缓冲区功能的函数指针组成,并且这是唯一需要的运行时信息。由doWork
。