GADT надають чіткий та кращий синтаксис коду за допомогою Existential Types, надаючи неявні forall
Я думаю, що існує загальна згода, що синтаксис 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.