Вопрос

Как использовать обобщенные алгебраические данные типа?

Пример, приведенный в Haskell Wikibook. слишком коротка, чтобы дать мне понимание реальных возможностей Гадта.

Это было полезно?

Решение

Я нашел «подсказку» монада (из пакета «Монадпромпта») очень полезный инструмент в нескольких местах (наряду с эквивалентной «программой» монадом из «оперативного» пакета. В сочетании с лампами (как это было предназначено для быть использованным), это позволяет сделать встроенные языки очень дешевы и очень гибко. Там была довольно хорошая статья в Монад читатель выпуск 15 Позвонил «Приключения в трех монаде», который имел хорошее введение в быстрый монад вместе с некоторыми реалистичными лампами.

Другие советы

Гадты являются слабыми приближениями индуктивных семейств от надежных языков, поэтому давайте начнем там.

Индуктивные семейства являются методом введения основных данных DataType на зависимому языку. Например, в Agda вы определяете естественные числа, такие как это

data Nat : Set where
  zero : Nat
  succ : Nat -> Nat 

что не очень фантазии, это по сути, просто то же самое, что и четкость HASKELL

data Nat = Zero | Succ Nat

и действительно в синтаксисе GADT. Форма Haskell еще более похожа

{-# LANGUAGE GADTs #-}

data Nat where
  Zero :: Nat
  Succ :: Nat -> Nat

Итак, сначала румяна, вы можете подумать, что гадты просто аккуратный дополнительный синтаксис. Это просто очень вершина айсберга.


У AGDA есть возможность представлять все виды типов незнакомых и странных для программиста Haskell. Простой - это тип конечных наборов. Этот тип написано как Fin 3 и представляет собой задавать чисел {0, 1, 2}. Отказ Так же, Fin 5 представляет набор чисел {0,1,2,3,4}.

Это должно быть довольно странным на данный момент. Во-первых, мы имеем в виду тип, который имеет регулярный номер как параметр «типа». Во-вторых, это не ясно, что это значит для Fin n представлять набор {0,1...n}. Отказ В реальной АГДА мы сделаем что-то более мощное, но достаточно сказать, что мы можем определить contains функция

contains : Nat -> Fin n -> Bool
contains i f = ?

Теперь это странно, потому что «естественное» определение contains было бы что-то вроде i < n, но n это значение, которое существует только в типе Fin n И мы не должны быть в состоянии пересечь, которые делятся так легко. Несмотря на то, что оказывается, что определение не так просто простое, это именно то, что питание, что индуктивные семьи имеют в зависимости от наведенных языков - они вводят значения, которые зависят от их типов и типов, которые зависят от их значений.


Мы можем изучить, о чем это Fin Это дает ему свойство, глядя на его определение.

data Fin : Nat -> Set where
  zerof : (n : Nat) -> Fin (succ n)
  succf : (n : Nat) -> (i : Fin n) -> Fin (succ n)

Это берет на себя небольшую работу, чтобы понять, так как пример позволяет попробовать построить значение типа Fin 2. Отказ Есть несколько способов сделать это (на самом деле, мы найдем, что имеется ровно 2)

zerof 1           : Fin 2
zerof 2           : Fin 3 -- nope!
zerof 0           : Fin 1 -- nope!
succf 1 (zerof 0) : Fin 2

Это позволяет нам увидеть, что есть два жителя, а также немного демонстрирует, как происходит вычисление типа. В частности, (n : Nat) бит в типе zerof отражает фактическое стоимость n в типе, позволяющий нам образовывать Fin (n+1) для любого n : Nat. Отказ После этого мы используем повторные приложения succf увеличить наш Fin значения в прямом эфире в правильном семейный индекс типа (естественное число, которое индексирует Fin).

Что обеспечивает эти способности? Во всей честности существует множество различий между зависимому типизированной индуктивной семьей и регулярным ADT Haskell, но мы можем сосредоточиться на том, что это имеет значение, наиболее актуально для понимания падах.

В лампах и индуктивных семьях вы получаете возможность указать точный Тип ваших конструкторов. Это может быть скучно

data Nat where
  Zero :: Nat
  Succ :: Nat -> Nat

Или, если у нас есть более гибкий, индексированный тип, мы можем выбрать разные, более интересные типы возврата

data Typed t where
  TyInt  :: Int                -> Typed Int
  TyChar :: Char               -> Typed Char
  TyUnit ::                       Typed ()
  TyProd :: Typed a -> Typed b -> Typed (a, b)
  ...

В частности, мы оскорбляем способность изменить тип возврата на основе особый Используемый конструктор ценности. Это позволяет нам отражать некоторую ценность информации в тип и создавать более тонко указанные (увоченные) набранные.


Так что мы можем сделать с ними? Ну, с небольшим количеством локтевого смазки мы можем производить Fin в хэшкелле. Отказ Кратко требует, чтобы мы определили понятие натуральных типов

data Z
data S a = S a

> undefined :: S (S (S Z))  -- 3

... затем гадт, чтобы отражать ценности в эти типы ...

data Nat where
  Zero :: Nat Z
  Succ :: Nat n -> Nat (S n)

... Тогда мы можем использовать их, чтобы построить Fin Многое, как мы сделали в Агда ...

data Fin n where
  ZeroF :: Nat n -> Fin (S n)
  SuccF :: Nat n -> Fin n -> Fin (S n)

И, наконец, мы можем построить ровно два значения Fin (S (S Z))

*Fin> :t ZeroF (Succ Zero)
ZeroF (Succ Zero) :: Fin (S (S Z))

*Fin> :t SuccF (Succ Zero) (ZeroF Zero)
SuccF (Succ Zero) (ZeroF Zero) :: Fin (S (S Z))

Но обратите внимание, что мы потеряли много удобства по индуктивным семьям. Например, мы не можем использовать обычные цифровые литералы в наших типах (хотя это технически просто трюк в Agda в любом случае), нам нужно создать отдельный «тип NAT» и «значение NAT» и использовать GADT, чтобы связать их вместе, И мы также нашли вовремя, что в то время как на уровне типа математика болезненна в AGDA, это может быть сделано. В Haskell это невероятно болезненно и часто не может.

Например, можно определить weaken Понятие в АГДА Fin тип

weaken : (n <= m) -> Fin n -> Fin m
weaken = ...

где мы предоставляем очень интересное первое значение, доказательство того, что n <= m который позволяет нам встроить «значение меньше, чем n«В набор« ценностей меньше, чем m«. Мы можем сделать то же самое в Haskell, технически, но это требует тяжелого злоупотребления типовым проловым классом.


Таким образом, Gadts - это сходство с индуктивными семействами на зависимому типовому языкам, которые являются слабыми и кругами. Почему мы хотим их в Haskell в первую очередь?

По сути, потому что не все инварианты типа требуют полной мощности индуктивных семейств в экспресс и гадты выбирают особый компромисс между выразительностью, реализуемостью в Haskell и введите вывод.

Некоторые примеры полезных высказываний гадтов являются Красно-черные деревья, которые не могут иметь недействительным красно-черного имущества или Просто напечатанный кальцилус лямбда, встроенный как Piggy-Browding от типа Haskell Type.

На практике вы также часто видите пады используют для их неявного экзистенциального контекста. Например, тип

data Foo where
  Bar :: a -> Foo

неявно скрывается a Тип переменной с использованием экзистенциальной оценки

> :t Bar 4 :: Foo

таким образом, что иногда удобно. Если вы посмотрите внимательно, пример HOAS из Wikipedia использует это для a Тип параметра в App конструктор. Чтобы выразить, что заявление без кадров будет беспорядок экзистенциальных контекстов, но синтаксис GADT делает его естественным.

Гадты могут дать вам усиленные гарантии, чем регулярные объявления. Например, вы можете заставить бинарное дерево быть сбалансированным на уровне системы типа, как в Это реализация из 2-3 деревьев:

{-# LANGUAGE GADTs #-}

data Zero
data Succ s = Succ s

data Node s a where
    Leaf2 :: a -> Node Zero a
    Leaf3 :: a -> a -> Node Zero a
    Node2 :: Node s a -> a -> Node s a -> Node (Succ s) a
    Node3 :: Node s a -> a -> Node s a -> a -> Node s a -> Node (Succ s) a

Каждый узел имеет глубину кодировки типа, где проживают все его листья. Дерево - это либо пустое дерево, синглтонное значение или узел неуточненной глубины, снова используя пады.

data BTree a where
    Root0 :: BTree a
    Root1 :: a -> BTree a
    RootN :: Node s a -> BTree a

Система типа гарантирует вам, что могут быть построены только сбалансированные узлы. Это означает, что при внедрении операций, как insert На таких деревьях ваш код типа проверки только в том случае, если его результат всегда является сбалансированным деревом.

Мне нравится пример в Руководство GHC. Отказ Это быстрое демоверсия идеи Core Gadt: что вы можете встроить систему типа языка, которую вы управляете в систему типа Haskell. Это позволяет вам предполагать ваши функции Haskell и заставляет их сохранить, что синтаксические деревья соответствуют хорошо набранным программам.

Когда мы определяем Term, не имеет значения, какие типы мы выберем. Мы могли бы написать

data Term a where
  ...
  IsZero :: Term Char -> Term Char

или

  ...
  IsZero :: Term a -> Term b

и определение Term все равно пройдет.

Это только однажды мы хотим вычислить дальше Term, например, в определении eval, что типы имеют значение. Нам нужно иметь

  ...
  IsZero :: Term Int -> Term Bool

потому что нам нужен наш рекурсивный призыв к eval вернуть Ань Int, и мы хотим в свою очередь, вернуть Bool.

Это короткий ответ, но обратитесь к Haskell Wikibook. Он идет вас, хотя и гадт для хорошо напечатанного дерева выражения, который является довольно каноническим примером: http://en.wikibooks.org/wiki/haskell/gadt.

Гадты также используются для реализации типа равенства: http://hackage.haskell.org/package/type-equality.. Отказ Я не могу найти подходящую статью для ссылки на эту отделку - эта техника до сих пор ударила в фольклоре. Это используется довольно хорошо, однако, в набранном олегам олега без тегами. См., Например, раздел на типизированном компиляции в пады. http://okmij.org/ftp/tagless-final/#tc-gadt.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top