В чем / почему разница между этими тремя? Является ли 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 ...
.
Я уже знаю, что GADT неудобны. Так что ни один из них не работает без явной подписи
inferGADT (MkGADT x) = x
inferFGADT (MkFGADT x) = x
Как вы говорите, таинственное послание «неприкасаемых». В своем комментарии «сопоставление этих конструкторов» я имел в виду следующее: компилятор «знает» по правой стороне уравнения, что полезная нагрузка равна 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 к объяснению с точки зрения дальнейших расширений это противоположно тому, что могло бы помочь учащемуся. Существующие ИМО в типах данных должны быть исключены: вместо этого используйте GADT. PatternSynonyms следует объяснять с точки зрения типов данных и функций сопоставления между ними, а не наоборот. О, и есть кое-что из DataKinds, которое я пропустил при первом чтении.)
data family
- это не более чем способ сгруппировать несколько объявлений нового типа / данных. Фактически, вы можете почти думать о семействе данных как о семействе инъективных открытых типов (где каждыйdata instance
соответствует объявлению данных, аtype instance
- этому объявлению). Эта аналогия распадается в двух местах (которые я могу придумать): конструкторы типов семейств данных могут быть частично применены, а конструкторы типов семейств данных имеют более строгие правила ввода (MyDataFamily a ~ g b
подразумеваетMyDataFamily ~ g
иa ~ b
, аMyInjectiveTyFam a ~ MyInjectiveTyFam b
подразумеваетa ~ b
). - person Alec   schedule 17.09.2018