различия: GADT, семейство данных, семейство данных, которое является GADT - PullRequest
0 голосов
/ 17 сентября 2018

В чем / почему различия между этими тремя? Является ли GADT (и обычные типы данных) просто сокращением для семейства данных? В чем конкретно разница:

data GADT a  where
  MkGADT :: Int -> GADT Int

data family FGADT a
data instance FGADT a  where             -- note not FGADT Int
  MkFGADT :: Int -> FGADT Int

data family DF a
data instance DF Int  where              -- using GADT syntax, but not a GADT
  MkDF :: Int -> DF Int

(Эти примеры слишком упрощены, поэтому я не вижу тонкости различий?)

Семейства данных расширяемы, а GADT - нет. OTOH экземпляры семейства данных не должны перекрываться. Поэтому я не мог объявить другой экземпляр / любые другие конструкторы для FGADT; точно так же, как я не могу объявить другие конструкторы для GADT. Я могу объявить другие экземпляры для DF.

При сопоставлении с образцом этих конструкторов правая часть уравнения «знает», что полезная нагрузка равна Int.

Для экземпляров классов (я был удивлен, обнаружив) я могу написать перекрывающиеся экземпляры для использования GADT:

instance C (GADT a) ...
instance {-# OVERLAPPING #-} C (GADT Int) ...

и аналогично для (FGADT a), (FGADT Int). Но не для (DF a): это должно быть для (DF Int) - это имеет смысл; нет data instance DF a, и если бы оно было, оно перекрывалось бы.

ДОБАВИТЬ: уточнить ответ @ kabuhr (спасибо)

вопреки тому, что, на мой взгляд, вы заявляете в части вашего вопроса, для простого семейства данных сопоставление в конструкторе не делает никакого вывода

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

inferDF (MkDF x) = x                 -- works without a signature

Логический тип inferDF :: DF Int -> Int имеет смысл. Давать ему подпись inferDF :: DF a -> a не имеет смысла: для data instance DF a ... нет декларации. Аналогично с foodouble :: Foo Char a -> a нет data instance Foo Char a ....

ГАДЦ неудобны, я уже знаю. Так что ни один из них не работает без явной подписи

inferGADT (MkGADT x) = x
inferFGADT (MkFGADT x) = x

Таинственное «неприкасаемое» сообщение, как вы говорите. Что я имел в виду в своем комментарии «сопоставление с этими конструкторами»: компилятор «знает» по rhs уравнения, что полезная нагрузка равна Int (для всех трех конструкторов), поэтому вам лучше получить любые сигнатуры, согласующиеся с этим.

Тогда я думаю, data GADT a where ... как будто data instance GADT a where .... Я могу дать подпись inferGADT :: GADT a -> a или inferGADT :: GADT Int -> Int (также для inferFGADT). Это имеет смысл: есть data instance GADT a ..., или я могу поставить подпись под более конкретным типом.

Таким образом, семейства данных являются обобщением GADT. Я также вижу, как вы говорите

Итак, в некотором смысле, GADT являются обобщениями семейств данных.

Хм. (Причиной этого вопроса является то, что GHC Haskell попал в стадию раздувания функций: слишком много похожих, но разных расширений. Я пытался сократить его до меньшего числа базовых абстракций. Затем подход @ HTNW к объяснению с точки зрения дальнейших расширений это противоречит тому, что могло бы помочь учащемуся. Должны быть исключены экзистенциалы IMO в типах данных: вместо этого используйте GADT. Шаблоны синонимов следует объяснять с точки зрения типов данных и функций отображения между ними, а не наоборот. О, и есть кое-что из DataKinds, которое я пропустил при первом чтении.

Ответы [ 2 ]

0 голосов
/ 17 сентября 2018

Я думаю, что разница станет ясной, если мы используем сигнатуры в стиле PatternSynonyms для конструкторов данных.Начнем с Haskell 98

data D a = D a a

Вы получаете тип шаблона:

pattern D :: forall a. a -> a -> D a

, его можно прочитать в двух направлениях.D в контексте «вперед» или выражения говорит: «forall a, вы можете дать мне 2 a с, и я дам вам D a».«В обратном направлении», как образец, написано: «forall a, вы можете дать мне D a, и я дам вам 2 a с».

Теперь, то, что вы пишете вопределение GADT не являются типами шаблонов.Кто они такие?Вранье.Ложь ложь ложь.Обращайте на них внимание только постольку, поскольку альтернатива выписывает их вручную с помощью ExistentialQuantification.Давайте использовать это

data GD a where
  GD :: Int -> GD Int

Вы получите

--                      vv ignore
pattern GD :: forall a. () => (a ~ Int) => Int -> GD a

Это говорит: forall a, вы можете дать мне GD a, и я могу дать вам доказательство того, что a ~ Int, плюс Int.

Важное замечание: Тип возврата / совпадения конструктора GADT равен всегда «заголовок типа данных».Я определил data GD a where ...;Я получил GD :: forall a. ... GD a.Это также верно для конструкторов Haskell 98, а также конструкторов data family, хотя это немного более тонко.

Если у меня есть GD a, и я не знаю, что такое a, яв любом случае может перейти в GD, хотя я написал GD :: Int -> GD Int, который, кажется, говорит, что я могу сопоставить его только с GD Int s.Вот почему я говорю, что конструкторы GADT лгут.Тип шаблона никогда не лжет.В нем четко говорится, что forall a, я могу сопоставить GD a с конструктором GD и получить свидетельство для a ~ Int и значение Int.

ОК, data family с.Давайте пока не смешиваем их с GADT.

data Nat = Z | S Nat
data Vect (n :: Nat) (a :: Type) :: Type where
  VNil :: Vect Z a
  VCons :: a -> Vect n a -> Vect (S n) a -- try finding the pattern types for these btw

data family Rect (ns :: [Nat]) (a :: Type) :: Type
newtype instance Rect '[] a = RectNil a
newtype instance Rect (n : ns) a = RectCons (Vect n (Rect ns a))

На самом деле сейчас есть две головки типа данных.Как говорит @KABuhr, разные data instance действуют как разные типы данных, которые просто случаются , чтобы разделить имя.Типы шаблонов:

pattern RectNil :: forall a. a -> Rect '[] a
pattern RectCons :: forall n ns a. Vect n (Rect ns a) -> Rect (n : ns) a

Если у меня есть Rect ns a, и я не знаю, что такое ns, Я не могу сопоставить его .RectNil занимает всего Rect '[] a с, RectCons занимает всего Rect (n : ns) a с.Вы можете спросить: «Зачем мне уменьшать власть?»@KABuhr дал один: ГАДЦы закрыты (и по уважительной причине; следите за обновлениями), семьи открыты.Это не относится к случаю Rect, так как эти экземпляры уже заполняют все пространство [Nat] * Type.Причина на самом деле newtype.

Вот ГАДТ RectG:

data RectG :: [Nat] -> Type -> Type where
  RectGN :: a -> RectG '[] a
  RectGC :: Vect n (RectG ns a) -> RectG (n : ns) a

Я получаю

-- it's fine if you don't get these
pattern RectGN :: forall ns a. () => (ns ~ '[]) => a -> RectG ns a
pattern RectGC :: forall ns' a. forall n ns. (ns' ~ (n : ns)) =>
                  Vect n (RectG ns a) -> RectG ns' a
-- just note that they both have the same matched type
-- which means there needs to be a runtime distinguishment 

Если у меня есть RectG ns a и донЯ не знаю, что такое ns, я все еще могу соответствовать ему.Компилятор должен сохранить эту информацию с помощью конструктора данных.Итак, если бы у меня был RectG [1000, 1000] Int, я бы взял на себя расходы на один миллион RectGN конструкторов, которые все «сохраняют» одну и ту же «информацию».Тем не менее, Rect [1000, 1000] Int - это нормально, поскольку у меня нет возможности сопоставить и сказать, является ли Rect RectNil или RectCons.Это позволяет конструктору быть newtype, так как он не содержит никакой информации.Вместо этого я бы использовал другой GADT, чем-то вроде

data SingListNat :: [Nat] -> Type where
  SLNN :: SingListNat '[]
  SLNCZ :: SingListNat ns -> SingListNat (Z : ns)
  SLNCS :: SingListNat (n : ns) -> SingListNat (S n : ns)

, который хранит размеры Rect в O(sum ns) пространстве вместо O(product ns) пространства (я думаю, что это правильно).Это также, почему GADT s закрыты, а семьи открыты.GADT похож на обычный data тип, за исключением того, что он имеет доказательства равенства и экзистенциальность.Не имеет смысла добавлять конструкторы в GADT больше, чем имеет смысл добавлять конструкторы в тип Haskell 98, потому что любой код, который не знает ни об одном из конструкторов, предназначен для очень плохое время.Это хорошо для семейства, потому что, как вы заметили, как только вы определите ветвь семейства, вы не сможете добавить больше конструкторов в эту ветвь.Как только вы знаете, в какой ветке вы находитесь, вы знаете конструкторы, и никто не может сломать это.Вам не разрешается использовать какие-либо конструкторы, если вы не знаете, какую ветвь использовать.

В ваших примерах не смешиваются GADT и семейства данных.Типы паттернов изящны тем, что они нормализуют поверхностные различия в data определениях, поэтому давайте посмотрим.

data family FGADT a
data instance FGADT a where
  MkFGADT :: Int -> FGADT Int

Дает вам

pattern MkFGADT :: forall a. () => (a ~ Int) => Int -> FGADT a
-- no different from a GADT; data family does nothing

Но

data family DF a
data instance DF Int where
  MkDF :: Int -> DF Int

дает

pattern MkDF :: Int -> DF Int
-- GADT syntax did nothing

Вот правильное смешивание

data family Map k :: Type -> Type
data instance Map Word8 :: Type -> Type where
  MW8BitSet :: BitSet8 -> Map Word8 Bool
  MW8General :: GeneralMap Word8 a -> Map Word8 a

Что дает шаблоны

pattern MW8BitSet :: forall a. () => (a ~ Bool) => BitSet8 -> Map Word8 a
pattern MW8General :: forall a. GeneralMap Word8 a -> Map Word8 a

Если у меня есть Map k v и я не знаю, что такое k, я не могу сопоставить его с MW8General или MW8BitSet, потому что те хотят только Map Word8 с. Это влияние data family. Если у меня есть Map Word8 v и я не знаю, что такое v, сопоставление по конструкторам может показать мне, известно ли, что это Bool или что-то еще.

0 голосов
/ 17 сентября 2018

Для начала вы должны думать о семействе данных как о коллекции независимых ADT, которые, как оказалось, индексируются по типу, в то время как GADT - это отдельный тип данных с выводимым параметром типа, в котором существуют ограничения на этот параметр (как правило,ограничения соответствия, такие как a ~ Int), могут быть введены в область действия путем сопоставления с образцом.

Это означает, что самое большое различие заключается в том, что, вопреки тому, что, я думаю, вы заявляете в части своего вопроса, для простых данныхсемейство, совпадающее с конструктором, не выполняет какой-либо вывод для параметра типа.В частности, эта проверка типов:

inferGADT :: GADT a -> a
inferGADT (MkGADT n) = n

, но это не :

inferDF :: DF a -> a
inferDF (MkDF n) = n

, и без сигнатур типов первая не сможет выполнить проверку типа (стаинственное «неприкасаемое» сообщение), в то время как второе будет выводиться как DF Int -> Int.

Ситуация становится немного более запутанной для чего-то типа вашего FGADT, который комбинирует семейства данных с GADT, и я признаюсь, чтона самом деле не думал о том, как это работает в деталях.Но, как интересный пример, рассмотрим:

data family Foo a b
data instance Foo Int a where
  Bar :: Double -> Foo Int Double
  Baz :: String -> Foo Int String
data instance Foo Char Double where
  Quux :: Double -> Foo Char Double
data instance Foo Char String where
  Zlorf :: String -> Foo Char String

В этом случае Foo Int a - это GADT с выводимым параметром a:

fooint :: Foo Int a -> a
fooint (Bar x) = x + 1.0
fooint (Baz x) = x ++ "ish"

, но Foo Char a - этопросто набор отдельных ADT, так что это не проверка типов:

foodouble :: Foo Char a -> a
foodouble (Quux x) = x

по той же причине inferDF не проверка типов выше.

Теперь вернемся к вашей простой *Типы 1033 * и GADT, вы можете в значительной степени эмулировать DFs, просто используя GADTs.Например, если у вас есть DF:

data family MyDF a
data instance MyDF Int where
  IntLit :: Int -> MyDF Int
  IntAdd :: MyDF Int -> MyDF Int -> MyDF Int
data instance MyDF Bool where
  Positive :: MyDF Int -> MyDF Bool

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

data MyGADT a where
  -- MyGADT Int
  IntLit' :: Int -> MyGADT Int
  IntAdd' :: MyGADT Int -> MyGADT Int -> MyGADT Int
  -- MyGADT Bool
  Positive' :: MyGADT Int -> MyGADT Bool

Так что, в некоторых отношениях, GADTобобщения семейств данных.Однако основной вариант использования для семейств данных - это определение связанных типов данных для классов:

class MyClass a where
  data family MyRep a
instance MyClass Int where
  data instance MyRep Int = ...
instance MyClass String where
  data instance MyRep String = ...

, где требуется "открытая" природа семейств данных (и где не используются методы логического вывода на основе шаблонов GADT).полезно).

...