Что такое GADT?

Я читал страницу GADTs для чайников на Haskell Wiki, и я до сих пор не понимаю, как и почему они должны использоваться. Автор привел мотивирующий пример:

data T a where
    D1 :: Int -> T String
    D2 :: T Bool
    D3 :: (a,a) -> T [a]

Что именно делает этот код и почему он полезен?

Если этот вопрос слишком расплывчатый, возможно, связанный с ним вопрос: могут ли GADT использоваться для реализации функций-членов?

Ответ 1

Предположим, вы хотите смоделировать фрукт-мешок. В этой сумке могут быть яблоко или апельсины. Итак, как хороший haskeller вы определяете:

data Bag = Oranges Int | Apples Int

Это выглядит хорошо. Давайте посмотрим, что тип Bag один, не глядя на конструкторы данных. Только тип Bag дает вам какие-либо указания, является ли это оранжевой сумкой или сумкой для яблока. Ну, не статически, я имею в виду, что во время выполнения функция могла бы сопоставлять шаблон по значению типа Bag, чтобы определить, являются ли они апельсинами или яблоками, но не будет ли это оправдано при компиляции времени/времени проверки типа. Таким образом, функция, которая работает только с сумкой яблок, не может быть передана сумкой с апельсинами вообще.

Здесь GADT могут помочь нам, в основном позволяет нам быть более точными в отношении наших типов:

data Orange = ...
data Apple = ....

data Bag a where
    OrangeBag :: [Orange] -> Bag [Orange]
    AppleBag :: [Apple] -> Bag [Apple]

Теперь я могу определить функцию, которая работает только с сумкой яблок.

giveMeApples :: Bag [Apple] -> ...

Ответ 2

GADT позволяют вам иметь ваши типы, чтобы содержать больше информации о значениях, которые они представляют. Они делают это, растягивая объявления Haskell data немного по пути к семействам индуктивных типов на языке с завязкой типизацией.

Пример квинтэссенции - типизированный синтаксис с более высоким порядком, представленный как GADT.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-} -- Not needed, just for convenience of (:@) below

module HOAS where

data Exp a where
  Lam  :: (Exp s -> Exp t) -> Exp (s -> t)
  (:@) :: Exp (s -> t) -> Exp s -> Exp t
  Con  :: a -> Exp a

intp :: Exp a -> a
intp (Con a)      = a
intp (Lam f)      = intp . f . Con
intp (fun :@ arg) = intp fun (intp arg)

В этом примере Exp является GADT. Обратите внимание, что конструктор Con является очень нормальным, но конструкторы App и Lam вводят новые типы переменных довольно свободно. Это экзистенциально квантованные переменные типа и представляют собой довольно сложные отношения между различными аргументами с Lam и App.

В частности, они гарантируют, что любой Exp можно интерпретировать как хорошо типизированное выражение Хаскелла. Без использования GADT нам нужно использовать типы сумм для представления значений в наших терминах и ошибок типа сообщения.

>>> intp $ Con (+1) :@ Con 1
2

>>> Con (+1) :@ Con 'a'
<interactive>:1:11: Warning:
    No instance for (Num Char) arising from a use of `+'
    Possible fix: add an instance declaration for (Num Char)
    In the first argument of `Con', namely `(+ 1)'
    In the first argument of `App', namely `(Con (+ 1))'
    In the expression: App (Con (+ 1)) (Con 'a')

>>> let konst = Lam $ \x -> Lam $ \y -> x
>>> :t konst
konst :: Exp (t -> s -> t)

>>> :t intp $ konst :@ Con "first"
intp $ konst :@ Con "first" :: s -> [Char]

>>> intp $ konst :@ Con "first" :@ Con "second"
"first"