GADT обеспечивают ясный и лучший синтаксис для кода с использованием экзистенциальных типов, предоставляя неявные поля
Я думаю, что есть общее согласие, что синтаксис GADT лучше. Я бы не сказал, что это потому, что GADT предоставляют неявные данные, а скорее потому, что оригинальный синтаксис, включенный с 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или 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качестве аргумента аргумент, она может использовать только Bufferсредства класса общего типа (например, функцию 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Типа выше, в addtion к наличию поля для буфера и входа, а также имеет невидимую неявное поле , которое указывает на Bufferсловарь (несколько , как у-таблицы, хотя это вряд ли огромный, так как он просто содержит указатель на соответствующую 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.